🔎 How to download & run the codes?

All the source codes of the aggregation methods are available here . To run the codes, you can clone the repository directly or simply load the R script source file from the repository using devtools package in Rstudio as follow:

  1. Install devtools package using command:

    install.packages("devtools")

  2. Loading the source codes from GitHub repository using source_url function by:

    devtools::source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/MixCobraReg.R")


✎ Note: All codes contained in this Rmarkdown are built with recent version of (version \(>\) 4.1, available here) and Rstudio (version > 2022.02.2+485, available here). Note also that the code chucks are hidden by default.

To see the codes, you can:


1 MixCobra & important packages

1.1 MixCobra method

This Rmarkdown provides the implementation of an aggregation method using input and out trade-off by Fischer and Mougeot (2019). Let \(\mathcal{D}_n=\{(x_1,y_1),...,(x_n,y_n)\}\) be a training data of size \(n\), where the input-output couples \((x_i,y_i)\in\mathbb{R}^d\times\mathbb{R}\) for all \(i=1,...,n\). \(\mathcal{D}_{n}\) is first randomly partitioned into \(\mathcal{D}_{k}\) and \(\mathcal{D}_{\ell}\) of size \(k\) and \(\ell\) respectively such that \(k+\ell=n\). We construct \(M\) regression estimators (machines) \(r_1,...,r_M\) using only \(\mathcal{D}_{k}\). Let \({\bf r}(x)=(r_1(x),...,r_M(x))^T\in\mathbb{R}^M\) be the vector of predictions of \(x\in\mathbb{R}^d\), the kernel-based consensual aggregation method evaluated at point \(x\) is defined by

\[\begin{equation} g_n(x)=\frac{\sum_{i=1}^{\ell}y_iK_{\alpha,\beta}(x-x_i,{\bf r}(x)-{\bf r}(x_i))}{\sum_{j=1}^{\ell}K_{\alpha,\beta}(x-x_j,{\bf r}(x)-{\bf r}(x_j))} \end{equation}\] where \(K:\mathbb{R}^{d+M}\to\mathbb{R}_+\) is a non-increasing kernel function with \(K_{\alpha,\beta}(u,v)=K(\frac{u}{\alpha},\frac{v}{\beta})\) for some smoothing parameter \(\alpha,\beta>0\) to be tuned, with the convention \(0/0=0\).

1.2 Important packages

We prepare all the necessary tools for this Rmarkdown. pacman package allows us to load (if exists) or install (if does not exist) any available packages from The Comprehensive R Archive Network (CRAN) of .

# Check if package "pacman" is already installed 

lookup_packages <- installed.packages()[,1]
if(!("pacman" %in% lookup_packages)){
  install.packages("pacman")
}

# To be installed or loaded
pacman::p_load(magrittr)
pacman::p_load(ggplot2)
pacman::p_load(tidyverse)

## package for "generateMachines"
pacman::p_load(tree)
pacman::p_load(glmnet)
pacman::p_load(randomForest)
pacman::p_load(FNN)
pacman::p_load(xgboost)
pacman::p_load(keras)
pacman::p_load(pracma)
pacman::p_load(latex2exp)
pacman::p_load(plotly)
pacman::p_load(dash)
rm(lookup_packages)

2 Basic Machine generator

This section provides functions to generate basic machines (regressors) to be aggregated.

2.1 Function : setBasicParameter_Mix

This function allows us to set the values of some key parameters of the basic machines.

  • Argument:

    • lambda : the penalty parameter \(\lambda\) used in penalized linear models: ridge or lasso.
    • k : the parameter \(k\) of \(k\)NN (knn) regression model and the default value is \(k=10\).
    • ntree : the number of trees in random forest (rf). By default, ntree = 300.
    • mtry : the number of random features chosen in each split of random forest procedure. By default, mtry = NULL and the default value of mtry of randomForest function from randomForest library is used.
    • eta_xgb : the learning rate \(\eta>0\) in gradient step of extreme gradient boosting method (xgb) of xgboost library.
    • nrounds_xgb : the parameter nrounds indicating the max number of boosting iterations. By default, nrounds_xgb = 100.
    • early_stop_xgb : the early stopping round criterion of xgboost function. By, default, early_stop_xgb = NULL and the early stopping function is not triggered.
    • max_depth_xgb : maximum depth of trees constructed in xgboost.
  • Value:

    This function returns a list of all the parameters given in its arguments, to be fed to the basicMachineParam argument of function generateMachines_Mix defined in the next section.


Remark.1: lambda, k, ntree can be a single value or a vector. In other words, each type of models can be constructed several times according to the values of the parameters \((\alpha, \beta)\) of the method.


setBasicParameter_Mix <- function(lambda = NULL,
                              k = 5, 
                              ntree = 300, 
                              mtry = NULL, 
                              eta_xgb = 1, 
                              nrounds_xgb = 100, 
                              early_stop_xgb = NULL,
                              max_depth_xgb = 3){
  return(list(
    lambda = lambda,
    k = k,
    ntree = ntree, 
    mtry = mtry, 
    eta_xgb = eta_xgb, 
    nrounds_xgb = nrounds_xgb, 
    early_stop_xgb = early_stop_xgb,
    max_depth_xgb = max_depth_xgb)
  )
}

2.2 Function : generateMachines_Mix

This function generates all the basic machines to be aggregated.

  • Argument:

    • train_input : a matrix or data frame of the training input data.
    • train_response : a vector of training response variable corresponding to the train_input.
    • scale_input : logical value specifying whether to scale the input data (to be between \(0\) and \(1\)) or not. By default, scale_input = TRUE.
    • scale_machine : logical value specifying whether to scale the predictions of the remaining part \(\mathcal{D}_{\ell}\) of the training data (to be between \(0\) and \(1\)) or not. By default, scale_machine = TRUE.
    • machines : types of basic machines to be constructed. It is a subset of {“lasso”, “ridge”, “knn”, “tree”, “rf”, “xgb”}. By default, machines = NULL and all the six types of basic machines are built.
    • splits : real number between \(0\) and \(1\) specifying the proportion of training data used to train the basic machines (\(\mathcal{D}_k\)). The remaining proportion of (\(1-\) splits) is used for the aggregation (\(\mathcal{D}_{\ell}\)). By default, splits = 0.5.
    • basicMachineParam : the option used to setup the values of parameters of each machines. One should feed the function setBasicParameter_Mix() defined above to this argument.
  • Value:

    This function returns a list of the following objects.

    • fitted_remain : the predictions of the remaining part (\(\mathcal{D}_{\ell}\)) of the training data used for the aggregation.
    • models : all the constructed basic machines (it contains only the values of prapeter \(k\) for knn).
    • id2 : a logical vector of size equals to the number of lines of the training data indicating the location of the points used to build the basic machines (FALSE) and the remaining ones (TRUE).
    • train_data : a list of the following objects:
      • train_input : training input data (scale or non-scaling accordingly).
      • predict_remain_org : predictions of the second part \({\cal D}_{\ell}\) of the training data without scaling.
      • train_response : the trainging response variable.
      • min_machine, max_machine : vectors of minimum and maximum values predicted values of the remaining part \(\mathcal{D}_{\ell}\) of the training data. They are NULL if scale_machine = FALSE.
      • min_input, max_input : vectors of minimum and maximum values of each variable of the training input. They are NULL if scale_input = FALSE.

✎ Note: You may need to modify the function accordingly if you want to build different types of basic machines.


generateMachines_Mix <- function(train_input, 
                             train_response,
                             scale_input = TRUE,
                             scale_machine = TRUE,
                             machines = NULL, 
                             splits = 0.5, 
                             basicMachineParam = setBasicParameter_Mix()){
  lambda = basicMachineParam$lambda
  k <- basicMachineParam$k 
  ntree <- basicMachineParam$ntree 
  mtry <- basicMachineParam$mtry
  eta_xgb <- basicMachineParam$eta_xgb 
  nrounds_xgb <- basicMachineParam$nrounds_xgb
  early_stop_xgb <- basicMachineParam$early_stop_xgb
  max_depth_xgb <- basicMachineParam$max_depth_xgb
  
  # Packages
  pacman::p_load(tree)
  pacman::p_load(glmnet)
  pacman::p_load(randomForest)
  pacman::p_load(FNN)
  pacman::p_load(xgboost)
  # pacman::p_load(keras)
  
  # Preparing data
  input_names <- colnames(train_input)
  input_size <- dim(train_input)
  df_input <- train_input_scale <- train_input
  maxs <- mins <- NULL
  if(scale_input){
    maxs <- map_dbl(.x = df_input, .f = max)
    mins <- map_dbl(.x = df_input, .f = min)
    train_input_scale <- scale(train_input, center = mins, scale = maxs - mins)
  }
  if(is.matrix(train_input_scale)){
    df_input <- as_tibble(train_input_scale)
    matrix_input <- train_input_scale
  } else{
    df_input <- train_input_scale
    matrix_input <- as.matrix(train_input_scale)
  }
  
  # Machines
  lasso_machine <- function(x, lambda0){
    if(is.null(lambda)){
      cv <- cv.glmnet(matrix_train_x1, train_y1, alpha = 1, lambda = 10^(seq(-3,2,length.out = 50)))
      mod <- glmnet(matrix_train_x1, train_y1, alpha = 1, lambda = cv$lambda.min)
    } else{
      mod <- glmnet(matrix_train_x1, train_y1, alpha = 1, lambda = lambda0)
    }
    res <- predict.glmnet(mod, newx = x)
    return(list(pred = res,
                model = mod))
  }
  ridge_machine <- function(x, lambda0){
    if(is.null(lambda)){
      cv <- cv.glmnet(matrix_train_x1, train_y1, alpha = 0, lambda = 10^(seq(-3,2,length.out = 50)))
      mod <- glmnet(matrix_train_x1, train_y1, alpha = 0, lambda = cv$lambda.min)
    } else{
      mod <- glmnet(matrix_train_x1, train_y1, alpha = 0, lambda = lambda0)
    }
    res <- predict.glmnet(mod, newx = x)
    return(list(pred = res,
                model = mod))
  }
  tree_machine <- function(x, pa = NULL) {
    mod <- tree(as.formula(paste("train_y1~", 
                                 paste(input_names, sep = "", collapse = "+"), 
                                 collapse = "", 
                                 sep = "")), 
                data = df_train_x1)
    res <- as.vector(predict(mod, x))
    return(list(pred = res,
                model = mod))
  }
  knn_machine <- function(x, k0) {
    mod <- knn.reg(train = matrix_train_x1, test = x, y = train_y1, k = k0)
    res = mod$pred
    return(list(pred = res,
                model = k0))
  }
  RF_machine <- function(x, ntree0) {
    if(is.null(mtry)){
      mod <- randomForest(x = df_train_x1, y = train_y1, ntree = ntree0)
    }else{
      mod <- randomForest(x = df_train_x1, y = train_y1, ntree = ntree0, mtry = mtry)
    }
    res <- as.vector(predict(mod, x))
    return(list(pred = res,
                model = mod))
  }
  xgb_machine = function(x, nrounds_xgb0){
    mod <- xgboost(data = matrix_train_x1, 
                   label = train_y1, 
                   eta = eta_xgb,
                   nrounds = nrounds_xgb0,
                   objective = "reg:squarederror",
                   early_stopping_rounds = early_stop_xgb,
                   max_depth = max_depth_xgb,
                   verbose = 0)
    res <- predict(mod, x)
    return(list(pred = res,
                model = mod))
  }
  
  # All machines
  all_machines <- list(lasso = lasso_machine, 
                       ridge = ridge_machine, 
                       knn = knn_machine, 
                       tree = tree_machine, 
                       rf = RF_machine,
                       xgb = xgb_machine)
  # All parameters
  all_parameters <- list(lasso = lambda, 
                         ridge = lambda, 
                         knn = k, 
                         tree = 1, 
                         rf = ntree,
                         xgb = nrounds_xgb)
  if(is.null(machines)){
    mach <- c("lasso", "ridge", "knn", "tree", "rf", "xgb")
  }else{
    mach <- machines
  }
  # Extracting data
  M <- length(mach)
  size_D1 <- floor(splits*input_size[1])
  id_D1 <- logical(input_size[1])
  id_D1[sample(input_size[1], size_D1)] <- TRUE
  
  df_train_x1 <- df_input[id_D1,]
  matrix_train_x1 <- matrix_input[id_D1,]
  train_y1 <- train_response[id_D1]
  df_train_x2 <- df_input[!id_D1,]
  matrix_train_x2 <- matrix_input[!id_D1,]
  
  # Function to extract df and model from 'map' function
  extr_df <- function(x, id){
    return(tibble("r_{{id}}":= as.vector(pred_m[[x]]$pred)))
  }
  extr_mod <- function(x, id){
    return(pred_m[[x]]$model)
  }
  
  pred_D2 <- c()
  all_mod <- c()
  cat("\n* Building basic machines ...\n")
  cat("\t~ Progress:")
  for(m in 1:M){
    if(mach[m] %in% c("tree", "rf")){
      x0_test <- df_train_x2
    } else {
      x0_test <- matrix_train_x2
    }
    if(is.null(all_parameters[[mach[m]]])){
      para_ <- 1
    }else{
      para_ <- all_parameters[[mach[m]]]
    }
    pred_m <-  map(para_, 
                   .f = ~ all_machines[[mach[m]]](x0_test, .x))
    tem0 <- imap_dfc(.x = 1:length(para_), 
                     .f = extr_df)
    tem1 <- imap(.x = 1:length(para_), 
                 .f = extr_mod)
    names(tem0) <- names(tem1) <- paste0(mach[m], 1:length(para_))
    pred_D2 <- bind_cols(pred_D2, as_tibble(tem0))
    all_mod[[mach[m]]] <- tem1
    cat(" ... ", round(m/M, 2)*100L,"%", sep = "")
  }
  max_M <- min_M <- NULL
  pred_D2_ <- pred_D2
  if(scale_machine){
    max_M <- map_dbl(.x = pred_D2, .f = max)
    min_M <- map_dbl(.x = pred_D2, .f = min)
    pred_D2 <- scale(pred_D2, center = min_M, scale = max_M - min_M)
  }
  return(list(fitted_remain = pred_D2,
              models = all_mod,
              id2 = !id_D1,
              train_data = list(train_input = train_input_scale, 
                                train_response = train_response,
                                predict_remain_org = pred_D2_,
                                min_machine = min_M,
                                max_machine = max_M,
                                min_input = mins,
                                max_input = maxs)))
}

Example.1: In this example, the method is implemented on Boston data of MASS library. The basic machines “rf”, “knn” and “xgb” are built on the first part of the training data (\(\mathcal{D}_{k}\)), and the Root Mean Square Errors (RMSE) evaluated on the second part of the training data (\(\mathcal{D}_{\ell}\)) used for aggregation) are reported.


pacman::p_load(MASS)
df <- Boston
basic_machines <- generateMachines_Mix(train_input = df[,1:13],
                 train_response = df[,14],
                 scale_input = TRUE,
                 machines = c("rf", "knn", "xgb"),
                 basicMachineParam = setBasicParameter_Mix(lambda = 1:10/10, 
                                                ntree = 10:20 * 25,
                                                k = c(2:10)))

* Building basic machines ...
    ~ Progress: ... 33% ... 67% ... 100%
basic_machines$train_data$predict_remain_org %>%
  sweep(1, df[basic_machines$id2, "medv"]) %>%
  .^2 %>%
  colMeans %>%
  t %>%
  sqrt %>%
  as_tibble

3 Optimization algorithm

This part provides functions to approximate the key parameters \((\alpha,\beta)\in(\mathbb{R}_+^*)^2\) of the aggregation. Two important optimization methods are implemented: gradient descent algorithm (grad) and grid search (grid).

3.1 Gradient descent algorithm

3.1.1 Function : setGradParameter_Mix

This function allows us to set the values of parameters needed to process the gradient descent algorithm to approximate the hyperparameter of the aggregation method.

  • Argument:

    • val_init : a 2D vector of initial value of gradient descent iteration. By default, val_init = NULL and the algorithm will select the best value (with smallest cost function) among alpha_range and beta_range values of parameters.
    • rate : the 2D real valued vector or a string of learning rate in gradent descent algorithm. By default, rate = NULL (or “auto”) and the value coef_auto = c(0.1, 0.1) will be used. It can also be a functional rate, which is a string element of {“logarithm”, “sqrtroot”, “linear”, “polynomial”, “exponential”}. Each rate is defined according to coef_ type arguments bellow.
    • alpha_range : a range vector of \(\alpha\) values to be considered as the initial value in gradient step. By default, alpha_range = seq(0.0001, 10, length.out = 5).
    • beta_range : a range vector of \(\beta\) values to be considered as the initial value in gradient step. By default, beta_range = seq(0.1, 50, length.out = 5).
    • max_iter : maximum itertaion of gradient descent algorithm. By default, max_iter = 300.
    • print_step : a logical value controlling whether to print the result of each gradient step or not in order to keep track of the algorithm. By default, print_step = TRUE.
    • print_result : a logical value controlling whether to print the result of the algorithm or not. By default, print_result = TRUE.
    • figure : a logical value controlling whether to plot a graphic of the result or not. By default, figure = TRUE.
    • coef_auto : the constant learning rate when rate = NULL. By default, coef_auto = c(1, 1).
    • coef_log : the coefficinet multiplying to the logarithmic increment of the learning rate, i.e., the rate is rate \(=\) coef_log\(\times \log(1+t)\) where \(t\) is the numer number of iteration. By default, coef_log = 1.
    • coef_sqrt : the coefficinet multiplying to the square root increment of the learning rate, i.e., the rate is rate \(=\) coef_sqrt\(\times \sqrt{t}\). By default, coef_sqrt = 1.
    • coef_lm : the coefficinet multiplying to the linear increment of the learning rate, i.e., the rate is rate \(=\) coef_lm\(\times t\). By default, coef_lm = 1.
    • deg_poly : the degree of the polynomial increment of the learning rate, i.e., the rate is rate \(=t^{\texttt{coef_poly}}\). By default, deg_poly = 2.
    • base_exp : the base of the exponential increment of the learning rate, i.e., the rate is rate \(=\) base_exp\(^t\). By default, base_exp = 1.5.
    • axes : names of \(x,y\) and \(z\)-axis respectively. By default, axes = c("alpha", "beta", "L1 norm of gradient").
    • title : the title of the plot. By default, title = NULL and the default title is Gradient step.
    • threshold : the threshold to stop the algorithm what the relative change is smaller than this value. By default, threshold = 1e-10.
  • Value:

    This function returns a list of all the parameters given in its arguments.

setGradParameter_Mix <- function(val_init = NULL,
                             rate = NULL, 
                             alpha_range = seq(0.0001, 10, length.out = 5),
                             beta_range = seq(0.1, 50, length.out = 5),
                             max_iter = 300, 
                             print_step = TRUE, 
                             print_result = TRUE,
                             figure = TRUE, 
                             coef_auto = c(0.1,0.1),
                             coef_log = 1,
                             coef_sqrt = 1,
                             coef_lm = 1,
                             deg_poly = 2,
                             base_exp = 1.5,
                             axes = c("alpha", "beta", "L1 norm of gradient"),
                             title = NULL,
                             threshold = 1e-10) {
  return(
    list(val_init = val_init,
      rate = rate,
      alpha_range = alpha_range,
      beta_range = beta_range,
      max_iter = max_iter,
      print_step = print_step,
      print_result = print_result,
      figure = figure,
      coef_auto = coef_auto,
      coef_log = coef_log,
      coef_sqrt = coef_sqrt,
      coef_lm = coef_lm,
      deg_poly = deg_poly,
      base_exp = base_exp,
      axes = axes,
      title = title,
      threshold = threshold
    )
  )
}

3.1.2 Function : gradOptimizer_Mix

This function performs gradient descent algorithm to approximate the minimizer of any given functions (convex or locally convex around its optimizer).

  • Argument:

    • obj_fun : the objective function for which its minimizer is to be estimated. It should take a 2D vector as an input.
    • setParameter : the control of gradient descent parameters which should be the function setGradParameter_Mix() defined earlier.
  • Value:

    This function returns a list of the following objects:

    • opt_param : the observed value of the minimizer.
    • opt_error : the value of the optimal risk.
    • all_grad : the vector of all the gradients collected during the walk of the algorithm.
    • all_param : the vector of all parameters collected during the walk of the algorithm.
    • run_time : the running time of the algorithm.
gradOptimizer_Mix <- function(obj_fun,
                          setParameter = setGradParameter_Mix()) {
  start.time <- Sys.time()
  # Optimization step:
  # ==================
  spec_print <- function(x, dig = 5) return(ifelse(x > 1e-6, 
                                          format(x, digit = dig, nsmall = dig), 
                                          format(x, scientific = TRUE, digit = dig, nsmall = dig)))
  collect_val <- c()
  gradients <- c()
  if (is.null(setParameter$val_init)){
    range_alp <- rep(setParameter$alpha_range, length(setParameter$beta_range))
    range_bet <- rep(setParameter$beta_range, length(setParameter$alpha_range))
    tem <- map2_dbl(.x = range_alp,
                    .y = range_bet,
                    .f = ~ obj_fun(c(.x, .y)))
    id0 <- which.min(tem)
    val <- val0 <- c(range_alp[id0], range_bet[id0])
    grad_ <- pracma::grad(
      f = obj_fun,
      x0 = val0,
      heps = .Machine$double.eps ^ (1 / 3))
  } else{
    val <- val0 <- setParameter$val_init
    grad_ <- pracma::grad(
      f = obj_fun, 
      x0 = val0, 
      heps = .Machine$double.eps ^ (1 / 3))
  }
  if(setParameter$print_step){
    cat("\n* Gradient descent algorithm ...")
    cat("\n  Step\t|  alpha    ;  beta   \t|  Gradient (alpha ; beta)\t|  Threshold \n")
    cat(" ", rep("-", 80), sep = "")
    cat("\n   0 \t| ", spec_print(val0[1])," ; ", spec_print(val0[2]),
        "\t| ", spec_print(grad_[1], 6), " ; ", spec_print(grad_[2], 5), 
        " \t| ", setParameter$threshold, "\n")
    cat(" ", rep("-",80), sep = "")
  }
  if (is.numeric(setParameter$rate)){
    lambda0 <- setParameter$rate / abs(grad_)
    rate_GD <- "auto"
  } else{
    r0 <- setParameter$coef_auto / abs(grad_)
    # Rate functions
    rate_func <- list(auto = r0, 
                      logarithm = function(i)  setParameter$coef_log * log(2 + i) * r0,
                      sqrtroot = function(i) setParameter$coef_sqrt * sqrt(i) * r0,
                      linear = function(i) setParameter$coef_lm * (i) * r0,
                      polynomial = function(i) i ^ setParameter$deg_poly * r0,
                      exponential = function(i) setParameter$base_exp ^ i * r0)
    rate_GD <- match.arg(setParameter$rate, 
                         c("auto", 
                          "logarithm", 
                          "sqrtroot", 
                          "linear", 
                          "polynomial", 
                          "exponential"))
    lambda0 <- rate_func[[rate_GD]]
  }
  i <- 0
  grad0 <- 10*grad_ 
  if (is.numeric(setParameter$rate) | rate_GD == "auto") {
    while (i < setParameter$max_iter) {
      if(any(is.na(grad_))){
        val0 <- c(runif(1, val0[1]*0.99, val0[1]*1.01), 
                  runif(1, val0[2]*0.99, val0[2]*1.01)) 
        grad_ = pracma::grad(
          f = obj_fun, 
          x0 = val0, 
          heps = .Machine$double.eps ^ (1 / 3)
       )
      }
      val <- val0 - lambda0 * grad_
      if (any(val < 0)){
        val[val < 0] <- val0[val < 0]/2
        lambda0[val < 0] <- lambda0[val < 0] / 2
      }
      if(i > 5){
        sign_ <- sign(grad_) != sign(grad0)
        if(any(sign_)){
          lambda0[sign_] = lambda0[sign_]/2
        }
      }
      relative <- sum(abs(val - val0)) / sum(abs(val0))
      test_threshold <- max(relative, sum(abs(grad_ - grad0)))
      if (test_threshold > setParameter$threshold){
        val0 <- val
        grad0 <- grad_
      } else{
        break
      }
      grad_ <- pracma::grad(
        f = obj_fun, 
        x0 = val0, 
        heps = .Machine$double.eps ^ (1 / 3)
      )
      i <- i + 1
      if(setParameter$print_step){
        cat("\n  ", i, "\t| ", spec_print(val[1], 4), " ; ", spec_print(val[2], 4), 
            "\t| ", spec_print(grad_[1], 5), " ; ", spec_print(grad_[2], 5), 
            "\t| ", test_threshold, "\r")
      }
      collect_val <- rbind(collect_val, val)
      gradients <- rbind(gradients, grad_)
    }
  }
  else{
    while (i < setParameter$max_iter) {
      if(any(is.na(grad_))){
        val0 <- c(runif(1, val0[1]*0.99, val0[1]*1.01), 
                  runif(1, val0[2]*0.99, val0[2]*1.01)) 
        grad_ = pracma::grad(
          f = obj_fun, 
          x0 = val0, 
          heps = .Machine$double.eps ^ (1 / 3)
       )
      }
      val <- val0 - lambda0(i) * grad_
      if (any(val < 0)){
        val[val < 0] <- val0[val < 0]/2
        r0[val < 0] <- r0[val < 0] / 2
      }
      if(i > 5){
        sign_ <- sign(grad_) != sign(grad0)
        if(any(sign_)){
          r0[sign_] <- r0[sign_] / 2
        }
      }
      relative <- sum(abs(val - val0)) / sum(abs(val0))
      test_threshold <- max(relative, sum(abs(grad_ - grad0)))
      if (test_threshold > setParameter$threshold){
        val0 <- val
        grad0 <- grad_
      }else{
        break
      }
      grad_ <- pracma::grad(
        f = obj_fun, 
        x0 = val0, 
        heps = .Machine$double.eps ^ (1 / 3)
      )
      if(setParameter$print_step){
        cat("\n  ", i, "\t| ", spec_print(val[1], 4), " ; ", spec_print(val[2], 4), 
            "\t| ", spec_print(grad_[1], 5), " ; ", spec_print(grad_[2], 5), 
            "\t| ", test_threshold, "\r")
      }
      i <- i + 1
      collect_val <- rbind(collect_val, val)
      gradients <- rbind(gradients, grad_)
    }
  }
  opt_ep <- val
  opt_risk <- obj_fun(opt_ep)
  if(setParameter$print_step){
    cat(rep("-", 80), sep = "")
    if(sum(abs(grad_)) == 0){
      cat("\n Stopped| ", spec_print(val[1], 4), " ; ", spec_print(val[2], 4), 
        "\t|\t ", 0, 
        "\t\t| ", test_threshold)
    }else{
      cat("\n Stopped| ", spec_print(val[1], 4), " ; ", spec_print(val[2], 4), 
        "\t| ", spec_print(grad_[1]), " ; ", spec_print(grad_[2]), 
        "\t| ", test_threshold)
    } 
  }
  if(setParameter$print_result){
    cat("\n ~ Observed parameter: (alpha, beta) = (", opt_ep[1], ", ", opt_ep[2], ") in",i, "itertaions.")
  }
  if (setParameter$figure) {
    if(is.null(setParameter$title)){
      tit <- paste("<b> L1 norm of gradient as a function of</b> (",
      setParameter$axes[1],",", 
      setParameter$axes[2], 
      ")")
    } else{
      tit <- setParameter$title
    }
    siz = length(collect_val[,1])
    fig <- tibble(x = collect_val[,1],
           y = collect_val[,2],
           z = apply(abs(gradients), 1, sum)) %>%
      plot_ly(x = ~x, y = ~y) %>% 
      add_trace(z = ~z,
                type = "scatter3d",
                mode = "lines",
                line = list(width = 6, 
                            color = ~z, 
                            colorscale = 'Viridis'),
                name = "Gradient step") %>%
      add_trace(x = c(opt_ep[1], opt_ep[1]),
                y = c(0, opt_ep[2]),
                z = ~c(z[siz], z[siz]),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#5E88FC", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#5E88FC", "#38DE25")),
                name = paste("Optimal",setParameter$axes[1])) %>%
      add_trace(x = c(0, opt_ep[1]),
                y = c(opt_ep[2], opt_ep[2]),
                z = ~c(z[siz], z[siz]),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#F31536", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#F31536", "#38DE25")),
                name = paste("Optimal",setParameter$axes[2]))  %>%
      add_trace(x = opt_ep[1],
                y = opt_ep[2],
                z = ~z[siz],
                type = "scatter3d",
                mode = 'markers',
                marker = list(
                  size = 5,
                  color = "#38DE25"),
                name = "Optimal point") %>%
      layout(title = list(text = tit,
                          x = 0.075, 
                          y = 0.925,
                          font = list(family = "Verdana",
                                      color = "#5E88FC")),
             legend = list(x = 100, y = 0.5),
             scene = list(
               xaxis = list(title = setParameter$axes[1]),
               yaxis = list(title = setParameter$axes[2]),
               zaxis = list( title = setParameter$axes[3])))
    fig %>% print
  }
  end.time = Sys.time()
  return(list(
    opt_param = opt_ep,
    opt_error = opt_risk,
    all_grad = gradients,
    all_param = collect_val,
    run_time = difftime(end.time, 
                        start.time, 
                        units = "secs")[[1]]
  ))
}

Example.2: Approximate \[(x^*,y^*)=\text{arg}\min_{x,y)\in\mathbb{R}^2}f(x,y),\] where \[f(x,y)=(x-1)^2(1+\sin^2(2.5(x-1)))+(y-1)^2(1+\sin^2(2.5(y-1)))\] Note that argument val_init is crucial since \(f\) is not convex.


object_func <- function(x) sum((x-1)^2*(1+sin(2.5*(x-1))^2))
p <- tibble::tibble(x = rep(seq(-4,6, length.out = 30),30), 
                      y = rep(seq(-3,7, length.out = 30), each = 30)) %>%
  mutate(z = map2_dbl(.x = x, .y = y, .f = ~ object_func(c(.x,.y)))) %>%
  plot_ly(x = ~x, y = ~y, z = ~z, type = "mesh3d") %>%
  add_trace(x = 1,
            y = 1,
            z = 0,
            type = "scatter3d", mode = "markers",
            name = "Optimal point") %>%
  layout(title = "Cost function")
show(p)
gd <- gradOptimizer_Mix(obj_fun = object_func,
                  setParameter = setGradParameter_Mix(val_init = c(2.4, 3.5),
                                                rate = "log",
                                                coef_auto = c(0.7, 0.7),
                                                print_step = TRUE,
                                                figure = TRUE,
                                                axes = c("x", "y")))

* Gradient descent algorithm ...
  Step  |  alpha    ;  beta     |  Gradient (alpha ; beta)  |  Threshold 
 --------------------------------------------------------------------------------
   0    |  2.40000  ;  3.50000  |  6.363771  ;  3.96922     |  1e-10 
 --------------------------------------------------------------------------------
   0    |  1.9148  ;  3.0148    |  0.79847  ;  1.51397  |  92.99696 

   1    |  1.8183  ;  2.7215    |  1.56931  ;  11.74586     |  8.020555 

   2    |  1.5790  ;  1.3607    |  2.50307  ;  1.48199  |  11.00273 

   3    |  1.1359  ;  0.9401    |  0.33091  ;  -1.2513e-01  |  11.19763 

   4    |  1.0707  ;  0.9796    |  0.14999  ;  -4.0947e-02  |  3.779282 

   5    |  1.0385  ;  0.9937    |  0.078525  ;  -1.2638e-02     |  0.2651093 

   6    |  1.0206  ;  0.9983    |  0.041395  ;  -3.3626e-03     |  0.09977258 

   7    |  1.0106  ;  0.9996    |  0.021197  ;  -7.565e-04  |  0.04640585 

   8    |  1.0052  ;  0.9999    |  0.010433  ;  -1.421e-04  |  0.02280361 

   9    |  1.0025  ;  1.0000    |  0.0049263  ;  -2.1917e-05    |  0.01137799 

   10   |  1.0011  ;  1.0000    |  0.0022329  ;  -2.7076e-06    |  0.005627254 

   11   |  1.0005  ;  1.0000    |  0.00097291  ;  -2.5805e-07   |  0.002712624 

   12   |  1.0002  ;  1.0000    |  0.00040805  ;  -1.7849e-08   |  0.001262474 

   13   |  1.0001  ;  1.0000    |  0.00016495  ;  -8.0023e-10   |  0.0005650944 

   14   |  1.0000  ;  1.0000    |  6.4339e-05  ;  -1.7661e-11   |  0.000243119 

   15   |  1.0000  ;  1.0000    |  2.4237e-05  ;  -1.2212e-14   |  0.0001006146 

   16   |  1.0000  ;  1.0000    |  8.8254e-06  ;  3.3307e-16    |  4.010183e-05 

   17   |  1.0000  ;  1.0000    |  3.1086e-06  ;  -1.1102e-16   |  1.541137e-05 

   18   |  1.0000  ;  1.0000    |  1.0599e-06  ;  -1.1102e-16   |  5.716742e-06 

   19   |  1.0000  ;  1.0000    |  3.5e-07  ;  -1.1102e-16  |  2.048728e-06 

   20   |  1.0000  ;  1.0000    |  1.1199e-07  ;  -1.1102e-16   |  7.09896e-07 

   21   |  1.0000  ;  1.0000    |  3.4741e-08  ;  -1.1102e-16   |  2.380033e-07 

   22   |  1.0000  ;  1.0000    |  1.0452e-08  ;  -1.1102e-16   |  7.72527e-08 

   23   |  1.0000  ;  1.0000    |  3.0504e-09  ;  -1.1102e-16   |  2.428952e-08 

   24   |  1.0000  ;  1.0000    |  8.6399e-10  ;  -1.1102e-16   |  7.401194e-09 

   25   |  1.0000  ;  1.0000    |  2.3754e-10  ;  -1.1102e-16   |  2.18645e-09 

   26   |  1.0000  ;  1.0000    |  6.3406e-11  ;  -1.1102e-16   |  6.264504e-10 

   27   |  1.0000  ;  1.0000    |  1.6436e-11  ;  -1.1102e-16   |  1.741309e-10 
--------------------------------------------------------------------------------
 Stopped|  1.0000  ;  1.0000    |  1.6436e-11  ;  -1.1102e-16   |  4.697043e-11
 ~ Observed parameter: (alpha, beta) = ( 1 ,  1 ) in 28 itertaions.

3.2 Grid search algorithm

3.2.1 Function : setGridParameter_Mix

  • Argument:

    • min_alpha : mininum value of \(\alpha\) in the grid. By defualt, min_alpha = 1e-5.
    • max_alpha : maxinum value of \(\alpha\) in the grid. By defualt, max_alpha = 5.
    • min_beta : mininum value of \(\beta\) in the grid. By defualt, min_beta = 0.1.
    • max_beta : maximum value of \(\beta\) in the grid. By defualt, max_alpha = 50.
    • n_alpha, n_beta = 30 : the number of \(\alpha\) and \(\beta\) respectively in the grid. By defualt, n_alpha = n_beta = 30.
    • parameters : the list of parameter \(alpha\) and \(\beta\) in case non-uniform grid is considered. It should be a list of two vectors containing the values of \(\alpha\) and \(\beta\) respectively. By default, parameters = NULL and the default uniform grid is used.
    • axes : names of \(x,y\) and \(z\)-axis respectively. By default, axes = c("alpha", "beta", "Risk").
    • title : the title of the plot. By default, title = NULL and the default title is Cross-validation risk as a function of \((\alpha, \beta)\).
    • print_result : a logical value specifying whether to print the observed result or not.
    • figure : a logical value specifying whether to plot the graphic of cross-validation error or not.
  • Value:

    This function returns a list of all the parameters given in its arguments.

setGridParameter_Mix <- function(min_alpha = 1e-5,
                             max_alpha = 5,
                             min_beta = 0.1,
                             max_beta = 50,
                             n_alpha = 30,
                             n_beta = 30,
                             parameters = NULL,
                             axes = c("alpha", "beta", "Risk"),
                             title = NULL,
                             print_result = TRUE,
                             figure = TRUE){
  return(list(min_alpha = min_alpha,
              max_alpha = max_alpha,
              min_beta = min_beta,
              max_beta = max_beta,
              n_alpha = n_alpha,
              n_beta = n_beta,
              axes = axes,
              title = title,
              parameters = parameters,
              print_result = print_result,
              figure = figure))
}

3.2.2 Function : gridOptimizer_Mix

  • Argument:

    • obj_fun : the objective function for which its minimizer is to be estimated. It should be a univarate function of real positive variables.
    • setParameter : the control of grid search algorithm parameters which should be the function setGridParameter_Mix() defined above.
  • Value:

    This function returns a list of the following objects:

    • opt_param : the observed value of the minimizer.
    • opt_error : the value of optimal risk.
    • all_risk : the vector of all the errors evaluated at all the values of considered parameters.
    • run.time : the running time of the algorithm.
gridOptimizer_Mix <- function(obj_func,
                         setParameter = setGridParameter_Mix()){
  t0 <- Sys.time()
  if(is.null(setParameter$parameters)){
    param_list <- list(alpha =  rep(seq(setParameter$min_alpha, 
                                        setParameter$max_alpha,
                                        length.out = setParameter$n_alpha), 
                                    setParameter$n_beta),
                       beta =  rep(seq(setParameter$min_beta, 
                                       setParameter$max_beta,
                                       length.out = setParameter$n_beta),
                                   each = setParameter$n_alpha))
  } else{
    param_list <- list(alpha = rep(setParameter$parameters[[1]], 
                                   length(setParameter$parameters[[2]])),
                       beta = rep(setParameter$parameters[[2]], 
                                   each = length(setParameter$parameters[[1]])))
  }
  risk <- map2_dbl(.x = param_list$alpha,
                   .y = param_list$beta,
                   .f = ~ obj_func(c(.x, .y)))
  id_opt <- which.min(risk)
  opt_ep <- c(param_list$alpha[id_opt], param_list$beta[id_opt])
  opt_risk <- risk[id_opt]
  if(setParameter$print_result){
    cat("\n* Grid search algorithm...", "\n ~ Observed parameter: (alpha, beta) = (", opt_ep[1], 
        ", ", 
        opt_ep[2], ")", 
        sep = "")
  }
  if(setParameter$figure){
    if(is.null(setParameter$title)){
      tit <- paste("<b> Cross-validation risk as a function of</b> (",
                   setParameter$axes[1],",", 
                   setParameter$axes[2],
                   ")")
    } else{
      tit <- setParameter$title
    }
    fig <- tibble(alpha = param_list$alpha, 
                  beta = param_list$beta,
                  risk = risk) %>%
      plot_ly(x = ~alpha, y = ~beta, z = ~risk, type = "mesh3d") %>%
      add_trace(x = c(opt_ep[1], opt_ep[1]),
                y = c(0, opt_ep[2]),
                z = c(opt_risk, opt_risk),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#5E88FC", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#5E88FC", "#38DE25")),
                name = paste("Optimal",setParameter$axes[1])) %>%
      add_trace(x = c(0, opt_ep[1]),
                y = c(opt_ep[2], opt_ep[2]),
                z = c(opt_risk, opt_risk),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#F31536", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#F31536", "#38DE25")),
                name = paste("Optimal",setParameter$axes[2]))  %>%
      add_trace(x = opt_ep[1],
                y = opt_ep[2],
                z = opt_risk,
                type = "scatter3d",
                mode = 'markers',
                marker = list(
                  size = 5,
                  color = "#38DE25"),
                name = "Optimal point") %>%
      layout(title = list(text = tit,
                          x = 0.075, 
                          y = 0.925,
                          font = list(family = "Verdana",
                                      color = "#5E88FC")),
             legend = list(x = 100, y = 0.5),
             scene = list(xaxis = list(title = setParameter$axes[1]),
                          yaxis = list(title = setParameter$axes[2]),
                          zaxis = list( title = setParameter$axes[3])))
    print(fig)
  }
  t1 <- Sys.time()
  return(list(opt_param = opt_ep,
              opt_error = opt_risk,
              all_risk = risk,
              run.time = difftime(t1, 
                        t0, 
                        units = "secs")[[1]])
  )
}

Example.2: Again with grid search.


grid <- gridOptimizer_Mix(obj_fun = object_func,
                     setParameter = setGridParameter_Mix(min_alpha = -2,
                                                     max_alpha = 4,
                                                     min_beta = -2,
                                                     max_beta = 4,
                                                     n_alpha = 150,
                                                     n_beta = 150,
                                                     axes = c("x", "y", "z"),
                                                     title = "z = f(x,y)",
                                                     figure = TRUE))

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1.020134, 1.020134)

3.3 \(\kappa\)-cross validation lost function

Constructing aggregation method is equivalent to approximating the optimal value of parameter \((\alpha,\beta)\in(\mathbb{R}_+^*)^2\) introduced in section 1.1 by minimizing some lost function. In this study, we propose \(\kappa\)-fold cross validation lost function defined by

\[\begin{equation} \label{eq:kappa} \varphi^{\kappa}(h)=\frac{1}{\kappa}\sum_{k=1}^{\kappa}\sum_{(x_j,y_j)\in F_k}(g_n(x_j)-y_j)^2 \end{equation}\] where

  • for any \(k=1,...,\kappa\), \(F_k\) denotes the \(k\)th validation fold.
  • \(g_n({\bf r}(x_j))\) is the prediction of \(x_j\) of \(F_k\), computed using the data points from the remaining part \({\cal D}_{\ell}-F_k\) by, \[g_n({\bf r}(x_j))=\frac{\sum_{(x_i,y_i)\in{\cal D}_{\ell}-F_k}y_iK_{\alpha, \beta}(x_j-x_i,{\bf r}(x_j)- {\bf r}(x_i))}{\sum_{(x_i,y_i)\in{\cal D}_{\ell}-F_k}K_{\alpha, \beta}(x_j-x_i,{\bf r}(x_j)- {\bf r}(x_i))}\]

3.4 Function: dist_matrix_Mix

This function computes different distances between data points of each training folds (\(\mathcal{D}_{\ell}-F_k\)) and the corresponding validation fold \(F_k\) for any \(k=1,\dots,\kappa\). The \(\kappa\) distance matrices \(D_k=(d[{\bf r}(x_i),{\bf r}(x_j)])_{i,j}\) for \(k=1,\dots,\kappa\), are computed, where the distance \(d\) is defined according to different types kernel functions.

  • Argument:

    • basicMachines : the basic machine object, which is an output of generateMachines_Mix function.
    • n_cv : the number \(\kappa\) of cross-validation folds. By default, n_cv = 5.
    • kernel : the kernel function used for the aggregation, which is an element of {“gaussian”, “epanechnikov”, “biweight”, “triweight”, “triangular”, “naive”}. By default, kernel = "gaussian".
  • Value:

    This functions returns a list of the following objects:

    • dist : a list of data frame (tibble) containing sublists corresponding to kernel functions used for the aggregation. Each sublist contains n_cv numbers of distance matrices \(D_k=(d[{\bf r}(x_i),{\bf r}(x_j)])_{i,j}\), for \(k=1,\dots,\kappa\), containing distances between the data points in valiation fold (along the columns) and the \(1-\kappa\) remaining folds of training data (along the rows). The type of distance matrices depends on the kernel used:
      • If kernel = naive, the distance matrices contain the maximum distance between data points, i.e., \[D_k=(\|{\bf r}(x_i)-{\bf r}(x_j)\|_{\max})_{i,j}\text{ for }k=1,\dots,\kappa.\]
      • If kernel = triangular, the distance matrices contain the \(L_1\) distance between data points, i.e., dist_matrix_Mix \[D_k=(\|{\bf r}(x_i)-{\bf r}(x_j)\|_1)_{i,j}\text{ for }k=1,\dots,\kappa.\]
      • Otherwise, the distance matrices contain the squared \(L_2\) distance between data points, i.e., \[D_k=(\|{\bf r}(x_i)-{\bf r}(x_j)\|_ 2^2)_{i,j}\text{ for }k=1,\dots,\kappa.\]
    • id_shuffle : the shuffled indices in cross-validation.
    • n_cv : the number \(\kappa\) of cross-validation folds.
dist_matrix_Mix <- function(basicMachines,
                        n_cv = 5,
                        kernel = "gausian",
                        id_shuffle = NULL){
  n <- nrow(basicMachines$fitted_remain)
  n_each_fold <- floor(n/n_cv)
  # shuffled indices
  if(is.null(id_shuffle)){
    shuffle <- 1:(n_cv-1) %>%
    rep(n_each_fold) %>%
    c(., rep(n_cv, n - n_each_fold * (n_cv - 1))) %>%
    sample
  }else{
    shuffle <- id_shuffle
  }
  # the prediction matrix D_l
  df_mach <- as.matrix(basicMachines$fitted_remain)
  df_input <- as.matrix(basicMachines$train_data$train_input[basicMachines$id2,])
  if(! (kernel %in% c("naive", "triangular"))){
    pair_dist <- function(M, N){
      n_N <- dim(N)
      n_M <- dim(M)
      res_ <- 1:nrow(N) %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums((M - matrix(rep(N[id,], n_M[1]), ncol = n_M[2], byrow = TRUE))^2)))))
      return(res_)
    }
  }
  if(kernel == "triangular"){
    pair_dist <- function(M, N){
      n_N <- dim(N)
      n_M <- dim(M)
      res_ <- 1:nrow(N) %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums(abs(M - matrix(rep(N[id,], n_M[1]), ncol = n_M[2], byrow = TRUE)))))))
      return(res_)
    }
  }
  if(kernel == "naive"){
    pair_dist <- function(M, N){
      n_N <- dim(N)
      n_M <- dim(M)
      res_ <- 1:nrow(N) %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(apply(abs(M - matrix(rep(N[id,], n_M[1]), ncol = n_M[2], byrow = TRUE)), 1, max)))))
      return(res_)
    }
  }
  L1 <- 1:n_cv %>%
      map(.f = (\(x) pair_dist(df_input[shuffle != x,],
                                df_input[shuffle == x,])))
  L2 <- 1:n_cv %>%
      map(.f = (\(x) pair_dist(df_mach[shuffle != x,],
                                df_mach[shuffle == x,])))
  return(list(dist_input = L1,
              dist_machine = L2,
              id_shuffle = shuffle,
              n_cv = n_cv))
}

Example.3: The method dist_matrix_Mix is implemented on the obtained basic machines built in Example.1 with the corresponding Gaussian kernel function.


dis <- dist_matrix_Mix(basicMachines = basic_machines,
            n_cv = 3,
            kernel = "gaussian")
dis$n_cv
[1] 3

Example.4: From the distance matrix, we can compute the error corresponding to Gaussian kernel function, then use both of the optimization methods to approximate the smoothing paramter in this case.


# Gaussian kernel
gaussian_kern <- function(.ep = c(.05, 0.005),
                          .dist_matrix,
                          .train_response2,
                          .inv_sigma = sqrt(.5),
                          .alpha = 2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(exp(- (x[1]*D1+x[2]*D2)^(.alpha/2)*.inv_sigma^.alpha))
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
}

# Kappa cross-validation error
cost_fun <- function(x,
                     .dist_matrix = dis,
                     .kernel_func = gaussian_kern,
                     .train_response2 = basic_machines$train_data$train_response[basic_machines$id2],
                     .inv_sigma = sqrt(.5),
                     .alpha = 2){
  return(.kernel_func(.ep = x,
                      .dist_matrix = .dist_matrix,
                      .train_response2 = .train_response2,
                      .inv_sigma = .inv_sigma,
                      .alpha = .alpha))
}
  • Gradient descent
# Optimization
opt_param_gd <- gradOptimizer_Mix(obj_fun = cost_fun,
                              setParameter = setGradParameter_Mix(rate = "linear",
                                                              print_step = TRUE,
                                                              print_result = TRUE,
                                                              figure = TRUE))

* Gradient descent algorithm ...
  Step  |  alpha    ;  beta     |  Gradient (alpha ; beta)  |  Threshold 
 --------------------------------------------------------------------------------
   0    |  5.00005  ;  25.05000     |  -9.41942e+01  ;  10.93488    |  1e-10 
 --------------------------------------------------------------------------------
   0    |  5.0000  ;  25.0500   |  -9.4194e+01  ;  10.93488     |  946.1615 

   1    |  5.1000  ;  24.9500   |  -9.131e+01  ;  11.31877  |  0.006655563 

   2    |  5.2939  ;  24.7430   |  -8.6439e+01  ;  12.06192     |  3.268387 

   3    |  5.5692  ;  24.4121   |  -8.1095e+01  ;  13.13964     |  5.614224 

   4    |  5.9136  ;  23.9314   |  -7.696e+01  ;  14.60412  |  6.421059 

   5    |  6.3221  ;  23.2636   |  -7.588e+01  ;  16.67578  |  5.599537 

   6    |  6.8055  ;  22.3486   |  -8.0208e+01  ;  19.79002     |  3.151806 

   7    |  7.4015  ;  21.0818   |  -9.209e+01  ;  24.26427  |  7.44213 

   8    |  8.1837  ;  19.3066   |  -1.0471e+02  ;  27.68279     |  16.35665 

   9    |  9.1841  ;  17.0281   |  -8.3609e+01  ;  19.78119     |  16.0361 

   10   |  10.0717  ;  15.2191  |  -4.3463e+01  ;  5.63261  |  29.00008 

   11   |  10.5793  ;  14.6525  |  -2.7187e+01  ;  0.059944     |  54.2954 

   12   |  10.9257  ;  14.6459  |  -2.0188e+01  ;  -1.5753e+00  |  21.8482 

   13   |  11.2043  ;  14.8332  |  -1.6161e+01  ;  -1.9218e+00  |  8.633915 

   14   |  11.4445  ;  14.9562  |  -1.2993e+01  ;  -2.2793e+00  |  4.37372 

   15   |  11.6514  ;  15.1126  |  -1.0721e+01  ;  -2.3503e+00  |  3.525631 

   16   |  11.8335  ;  15.2845  |  -9.0021e+00  ;  -2.2716e+00  |  2.343102 

   17   |  11.9960  ;  15.4611  |  -7.6493e+00  ;  -2.1181e+00  |  1.79762 

   18   |  12.1421  ;  15.6354  |  -6.553e+00  ;  -1.9316e+00   |  1.506288 

   19   |  12.2743  ;  15.8033  |  -5.6449e+00  ;  -1.7357e+00  |  1.282802 

   20   |  12.3942  ;  15.9620  |  -4.8802e+00  ;  -1.5433e+00  |  1.104064 

   21   |  12.5030  ;  16.1102  |  -4.2286e+00  ;  -1.3616e+00  |  0.9569584 

   22   |  12.6017  ;  16.2472  |  -3.6681e+00  ;  -1.1938e+00  |  0.8334344 

   23   |  12.6913  ;  16.3727  |  -3.1829e+00  ;  -1.0414e+00  |  0.7282068 

   24   |  12.7724  ;  16.4870  |  -2.7608e+00  ;  -9.045e-01   |  0.6376106 

   25   |  12.8457  ;  16.5904  |  -2.3925e+00  ;  -7.8252e-01  |  0.5589861 

   26   |  12.9117  ;  16.6834  |  -2.0703e+00  ;  -6.7455e-01  |  0.4903353 

   27   |  12.9711  ;  16.7667  |  -1.7883e+00  ;  -5.7949e-01  |  0.4301108 

   28   |  13.0242  ;  16.8409  |  -1.5413e+00  ;  -4.9619e-01  |  0.3770935 

   29   |  13.0717  ;  16.9067  |  -1.3251e+00  ;  -4.2349e-01  |  0.3302988 

   30   |  13.1139  ;  16.9648  |  -1.136e+00  ;  -3.6027e-01   |  0.288921 

   31   |  13.1513  ;  17.0159  |  -9.7093e-01  ;  -3.055e-01   |  0.2522949 

   32   |  13.1843  ;  17.0606  |  -8.2709e-01  ;  -2.582e-01   |  0.2198576 

   33   |  13.2132  ;  17.0995  |  -7.0207e-01  ;  -2.175e-01   |  0.1911369 

   34   |  13.2386  ;  17.1333  |  -5.9373e-01  ;  -1.8258e-01  |  0.1657219 

   35   |  13.2606  ;  17.1625  |  -5.0014e-01  ;  -1.5273e-01  |  0.1432608 

   36   |  13.2797  ;  17.1877  |  -4.1958e-01  ;  -1.2729e-01  |  0.1234433 

   37   |  13.2962  ;  17.2092  |  -3.5049e-01  ;  -1.057e-01   |  0.1059967 

   38   |  13.3104  ;  17.2276  |  -2.915e-01  ;  -8.743e-02    |  0.09067661 

   39   |  13.3224  ;  17.2432  |  -2.4134e-01  ;  -7.203e-02   |  0.07726385 

   40   |  13.3327  ;  17.2564  |  -1.9888e-01  ;  -5.9099e-02  |  0.06556184 

   41   |  13.3413  ;  17.2674  |  -1.6311e-01  ;  -4.8284e-02  |  0.05539015 

   42   |  13.3486  ;  17.2767  |  -1.3312e-01  ;  -3.9276e-02  |  0.04658347 

   43   |  13.3547  ;  17.2844  |  -1.0811e-01  ;  -3.1805e-02  |  0.03899416 

   44   |  13.3597  ;  17.2908  |  -8.736e-02  ;  -2.5636e-02   |  0.03248192 

   45   |  13.3639  ;  17.2961  |  -7.0229e-02  ;  -2.0566e-02  |  0.02692221 

   46   |  13.3673  ;  17.3004  |  -5.6165e-02  ;  -1.6419e-02  |  0.02220052 

   47   |  13.3701  ;  17.3040  |  -4.4682e-02  ;  -1.3043e-02  |  0.01821102 

   48   |  13.3724  ;  17.3068  |  -3.5358e-02  ;  -1.0309e-02  |  0.01485913 

   49   |  13.3743  ;  17.3091  |  -2.783e-02  ;  -8.1066e-03   |  0.01205797 

   50   |  13.3757  ;  17.3110  |  -2.1786e-02  ;  -6.3411e-03  |  0.009730487 

   51   |  13.3769  ;  17.3125  |  -1.6962e-02  ;  -4.9339e-03  |  0.00780939 

   52   |  13.3779  ;  17.3136  |  -1.3133e-02  ;  -3.8184e-03  |  0.006231336 

   53   |  13.3786  ;  17.3146  |  -1.0112e-02  ;  -2.9387e-03  |  0.004944283 

   54   |  13.3792  ;  17.3153  |  -7.7429e-03  ;  -2.2498e-03  |  0.003900583 

   55   |  13.3796  ;  17.3159  |  -5.8952e-03  ;  -1.7123e-03  |  0.003058406 

   56   |  13.3800  ;  17.3163  |  -4.4629e-03  ;  -1.2958e-03  |  0.002385123 

   57   |  13.3802  ;  17.3166  |  -3.3596e-03  ;  -9.7547e-04  |  0.001848779 

   58   |  13.3805  ;  17.3169  |  -2.5142e-03  ;  -7.3013e-04  |  0.001423692 

   59   |  13.3806  ;  17.3171  |  -1.8708e-03  ;  -5.431e-04   |  0.001090749 

   60   |  13.3807  ;  17.3172  |  -1.3842e-03  ;  -4.0173e-04  |  0.0008304242 

   61   |  13.3808  ;  17.3173  |  -1.0177e-03  ;  -2.9554e-04  |  0.0006280374 

   62   |  13.3809  ;  17.3174  |  -7.4414e-04  ;  -2.159e-04   |  0.0004726614 

   63   |  13.3809  ;  17.3175  |  -5.4074e-04  ;  -1.5718e-04  |  0.0003531819 

   64   |  13.3810  ;  17.3175  |  -3.9077e-04  ;  -1.1328e-04  |  0.0002621266 

   65   |  13.3810  ;  17.3176  |  -2.8045e-04  ;  -8.1368e-05  |  0.0001938633 

   66   |  13.3810  ;  17.3176  |  -2.0006e-04  ;  -5.805e-05   |  0.000142234 

   67   |  13.3810  ;  17.3176  |  -1.4216e-04  ;  -4.1454e-05  |  0.0001037092 

   68   |  13.3810  ;  17.3176  |  -1.0033e-04  ;  -2.895e-05   |  7.449637e-05 

   69   |  13.3811  ;  17.3176  |  -7.0178e-05  ;  -2.0389e-05  |  5.433279e-05 

   70   |  13.3811  ;  17.3176  |  -4.8963e-05  ;  -1.4156e-05  |  3.871258e-05 

   71   |  13.3811  ;  17.3176  |  -3.3906e-05  ;  -9.8002e-06  |  2.744801e-05 

   72   |  13.3811  ;  17.3176  |  -2.3017e-05  ;  -6.9465e-06  |  1.941261e-05 

   73   |  13.3811  ;  17.3177  |  -1.6108e-05  ;  -4.3932e-06  |  1.374278e-05 

   74   |  13.3811  ;  17.3177  |  -1.0776e-05  ;  -2.9663e-06  |  9.46224e-06 

   75   |  13.3811  ;  17.3177  |  -7.1342e-06  ;  -2.1778e-06  |  6.758743e-06 

   76   |  13.3811  ;  17.3177  |  -4.8438e-06  ;  -1.3517e-06  |  4.430732e-06 

   77   |  13.3811  ;  17.3177  |  -3.1541e-06  ;  -9.7626e-07  |  3.116532e-06 

   78   |  13.3811  ;  17.3177  |  -2.1778e-06  ;  -8.6362e-07  |  2.065172e-06 

   79   |  13.3811  ;  17.3177  |  -1.577e-06  ;  -3.7549e-07   |  1.088909e-06 

   80   |  13.3811  ;  17.3177  |  -7.8852e-07  ;  -1.8774e-07  |  1.088909e-06 

   81   |  13.3811  ;  17.3177  |  -6.0078e-07  ;  -1.8774e-07  |  9.762629e-07 

   82   |  13.3811  ;  17.3177  |  -4.8813e-07  ;  -2.6284e-07  |  1.877429e-07 

   83   |  13.3811  ;  17.3177  |  -3.7549e-07  ;  -1.8774e-07  |  1.877429e-07 

   84   |  13.3811  ;  17.3177  |  -2.6284e-07  ;  0e+00    |  1.877429e-07 

   85   |  13.3811  ;  17.3177  |  -7.5097e-08  ;  -1.1265e-07  |  3.003886e-07 

   86   |  13.3811  ;  17.3177  |  0e+00  ;  7.5097e-08     |  3.003886e-07 

   87   |  13.3811  ;  17.3177  |  -1.1265e-07  ;  0e+00    |  2.6284e-07 

   88   |  13.3811  ;  17.3177  |  7.5097e-08  ;  7.5097e-08    |  1.877429e-07 

   89   |  13.3811  ;  17.3177  |  -3.7549e-08  ;  1.8774e-07   |  2.6284e-07 

   90   |  13.3811  ;  17.3177  |  0e+00  ;  7.5097e-08     |  2.252914e-07 

   91   |  13.3811  ;  17.3177  |  1.1265e-07  ;  1.1265e-07    |  1.501943e-07 

   92   |  13.3811  ;  17.3177  |  -3.7549e-08  ;  7.5097e-08   |  1.501943e-07 

   93   |  13.3811  ;  17.3177  |  1.8774e-07  ;  1.5019e-07    |  1.877429e-07 

   94   |  13.3811  ;  17.3177  |  7.5097e-08  ;  2.6284e-07    |  3.003886e-07 

   95   |  13.3811  ;  17.3177  |  -1.1265e-07  ;  0e+00    |  2.252914e-07 

   96   |  13.3811  ;  17.3177  |  -1.1265e-07  ;  1.5019e-07   |  4.505829e-07 

   97   |  13.3811  ;  17.3177  |  7.5097e-08  ;  1.5019e-07    |  1.501943e-07 

   98   |  13.3811  ;  17.3177  |  1.5019e-07  ;  1.5019e-07    |  1.877429e-07 

   99   |  13.3811  ;  17.3177  |  -7.5097e-08  ;  0e+00    |  7.509715e-08 

   100  |  13.3811  ;  17.3177  |  3.7549e-08  ;  -3.7549e-08   |  3.754857e-07 

   101  |  13.3811  ;  17.3177  |  -3.7549e-08  ;  1.5019e-07   |  1.501943e-07 

   102  |  13.3811  ;  17.3177  |  -3.7549e-08  ;  1.5019e-07   |  2.6284e-07 
--------------------------------------------------------------------------------
 Stopped|  13.3811  ;  17.3177  |  -3.7549e-08  ;  1.5019e-07   |  2.26654e-12
 ~ Observed parameter: (alpha, beta) = ( 13.38107 ,  17.31766 ) in 103 itertaions.
  • Grid search
# Optimization
opt_param_grid <- gridOptimizer_Mix(obj_fun = cost_fun,
                                setParameter = setGridParameter_Mix(min_alpha = 0.0001,
                                                                max_alpha = 20,
                                                                min_beta = 0.001,
                                                                max_beta = 100,
                                                                n_beta = 30,
                                                                n_alpha = 30,
                                                                figure = TRUE))

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (13.10348, 17.24221)
cat('* Observed parameter:\n\t - Gradient descent\t: (alpha, beta) = (', 
    opt_param_gd$opt_param[1],",",opt_param_gd$opt_param[2], ")",
    '\t with error:', cost_fun(opt_param_gd$opt_param),
    '\n\t - Grid search\t\t: (alpha, beta) = (',opt_param_grid$opt_param[1],",",
    opt_param_grid$opt_param[2],
    ')  \t with error:', cost_fun(opt_param_grid$opt_param))
* Observed parameter:
     - Gradient descent : (alpha, beta) = ( 13.38107 , 17.31766 )    with error: 3283.809 
     - Grid search      : (alpha, beta) = ( 13.10348 , 17.24221 )    with error: 3284.017

3.5 Fitting parameter

This function gathers the constructed machines and perform an optimization algorithm to approximate the smoothing parameter for the aggregation, using only the remaining part \({\cal D}_{\ell}\) of the training data.

  • Argument:

    • train_input, : a matrix or data frame of the training input data.
    • train_response : a vector of the corresponding response variable of the train_input.
    • machines : a vector of basic machines to be constructed. It must be a subset of {“lasso”, “ridge”, “knn”, “tree”, “rf”, “xgb”}. By default, machines = NULL and all the six types of basic machines are built.
    • scale_input : a logical value specifying whether or not to scale the input data before building the basic regression predictors. By default, scale_input = TRUE.
    • scale_machine : a logical value specifying whether or not to scale the predicted features given by all the basic regression predictors, for aggregation. By default, scale_machine = TRUE.
    • splits : the proportion of training data (the proportion of \({\cal D}_k\) in \({\cal D}_n\)) used to build the basic machines. By default, splits = .5.
    • n_cv : the number of cross-validation folds used to tune the smoothing parameter.
    • inv_sigma, alpha : the inverse normalized constant \(\sigma^{-1}>0\) and the exponent \(\alpha>0\) of exponential kernel: \(K(x)=e^{-\|x/\sigma\|^{\alpha}}\) for any \(x\in\mathbb{R}^d\). By default, inv_sigma =\(\sqrt{1/2}\) and alpha = 2 which corresponds to the Gaussian kernel.
    • kernels : the kernel function or vector of kernel functions used for the aggregation. By fault, kernels = "gaussian".
    • optimizeMethod : the optimization methods used to learn the smoothing parameter. By default, optimizeMethod = "grad" which stands for gradient descent algorithm, and if optimizeMethod = "grid", then the grid search algorithm is used. Note that this should be of the same size as the kernels argument, otherwise “grid” method will be used for all the kernels.
    • setBasicMachineParam : an option used to set the values of the parameters of the basic machines. setBasicParameter_Mix function should be fed to this argument.
    • setGradParam : an option used to set the values of the parameters of the gradient descent algorithm. setGradParameter_Mix function should be fed to it.
    • setGridParam : an option used to set the values of the parameters of the grid search algorithm. setGridParameter_Mix function should be fed to it.
  • Value:

    This function returns a list of the following objects:

    • opt_parameters : the observed optimal parameters.
    • add_parameters : other aditional parameters such as scaling options, parameters of kernel functions and the optimization methods used.
    • basic_machines : the list of basic machine object.
fit_parameter_Mix <- function(train_input, 
                          train_response,
                          train_predictions = NULL,
                          machines = NULL, 
                          scale_input = TRUE,
                          scale_machine = TRUE,
                          splits = 0.5, 
                          n_cv = 5,
                          inv_sigma = sqrt(.5),
                          alp = 2,
                          kernels = "gaussian",
                          optimizeMethod = "grad",
                          setBasicMachineParam = setBasicParameter_Mix(),
                          setGradParam = setGradParameter_Mix(),
                          setGridParam = setGridParameter_Mix()){
  kernels_lookup <- c("gaussian", "epanechnikov", "biweight", "triweight", "triangular", "naive")
  kernel_real <- kernels %>%
    sapply(FUN = function(x) return(match.arg(x, kernels_lookup)))
  if(is.null(train_predictions)){
    mach2 <- generateMachines_Mix(train_input = train_input,
                              train_response = train_response,
                              scale_input = scale_input,
                              scale_machine = scale_machine,
                              machines = machines,
                              splits = splits,
                              basicMachineParam = setBasicMachineParam)
  }else{
    mach2 <- list(fitted_remain = train_predictions,
                  models = NULL,
                  id2 = rep(TRUE, nrow(train_input)),
                  train_data = list(train_input = train_input, 
                                    train_response = train_response,
                                    predict_remain_org = train_predictions,
                                    min_machine = NULL,
                                    max_machine = NULL,
                                    min_input = NULL,
                                    max_input = NULL))
    if(scale_machine){
      min_ <- map_dbl(train_predictions, .f = min)
      max_ <- map_dbl(train_predictions, .f = max)
      mach2$train_data$min_machine = min_
      mach2$train_data$max_amchine = max_
      mach2$fitted_remain <- scale(train_predictions, 
                                   center = min_, 
                                   scale = max_ - min_)
    }
    if(scale_input){
      min_ <- map_dbl(train_input, .f = min)
      max_ <- map_dbl(train_input, .f = max)
      mach2$train_data$min_input = min_
      mach2$train_data$max_input = max_
      mach2$train_data$train_input <- scale(train_input, 
                                            center = min_, 
                                            scale = max_ - min_)
    }
  }
  # distance matrix to compute loss function
  if_euclid <- FALSE
  id_euclid <- NULL
  n_ker <- length(kernels)
  dist_all <- list()
  id_shuf <- NULL
  for (k_ in 1:n_ker){
    ker <- kernel_real[k_]
    if(ker == "naive"){
      dist_all[["naive"]] <- dist_matrix_Mix(basicMachines = mach2,
                                         n_cv = n_cv,
                                         kernel = "naive",
                                         id_shuffle = id_shuf)
    } else{
      if(ker == "triangular"){
        dist_all[["triangular"]] <- dist_matrix_Mix(basicMachines = mach2,
                                                n_cv = n_cv,
                                                kernel = "triangular",
                                                id_shuffle = id_shuf)
      } else{
        if(if_euclid){
          dist_all[[ker]] <- dist_all[[id_euclid]]
        } else{
          dist_all[[ker]] <- dist_matrix_Mix(basicMachines = mach2,
                                         n_cv = n_cv,
                                         kernel = ker,
                                         id_shuffle = id_shuf)
          id_euclid <- ker
          if_euclid <- TRUE
        }
      }
    }
    id_shuf <- dist_all[[1]]$id_shuffle
  }
  # Kernel functions 
  # ================
  # Gaussian
  gaussian_kernel <- function(.ep,
                              .dist_matrix,
                              .train_response2,
                              .inv_sigma = inv_sigma,
                              .alpha = alp){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(exp(- (x[1]*D1+x[2]*D2)^(.alpha/2)*.inv_sigma^.alpha))
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
}

# Epanechnikov
  epanechnikov_kernel <- function(.ep,
                                  .dist_matrix,
                                  .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D))
    tem0[tem0 < 0] = 0
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
  }

# Biweight
  biweight_kernel <- function(.ep,
                              .dist_matrix,
                              .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] = 0
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0^2/colSums(tem0^2)
    y_hat[is.na(y_hat)] <- 0
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
  }

# Triweight
  triweight_kernel <- function(.ep,
                               .dist_matrix,
                               .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] = 0
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0^3/colSums(tem0^3)
    y_hat[is.na(y_hat)] <- 0
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
  }

# Triangular
  triangular_kernel <- function(.ep,
                                .dist_matrix,
                                .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] <- 0
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
    y_hat[is.na(y_hat)] = 0
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
  }

  # Naive
  naive_kernel <- function(.ep,
                                .dist_matrix,
                                .train_response2){
    kern_fun <- function(x, id, D1, D2){
      tem0 <- (as.matrix((x[1]*D1+x[2]*D2)) < 1)
      y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
      y_hat[is.na(y_hat)] = 0
      return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
    }
    temp <- map(.x = 1:.dist_matrix$n_cv, 
                .f = ~ kern_fun(x = .ep, 
                                id = .x,
                                D1 = .dist_matrix$dist_input[[.x]], 
                                D2 = .dist_matrix$dist_machine[[.x]]))
    return(Reduce("+", temp))
  }
  
  # list of kernel functions
  list_funs <- list(gaussian = gaussian_kernel,
                    epanechnikov = epanechnikov_kernel,
                    biweight = biweight_kernel,
                    triweight = triweight_kernel,
                    triangular = triangular_kernel,
                    naive = naive_kernel)
  
  # error for all kernel functions
  error_func <- kernel_real %>%
    map(.f = ~ (\(x) list_funs[[.x]](.ep = x,
                                     .dist_matrix = dist_all[[.x]],
                                     .train_response2 = train_response[mach2$id2])/n_cv))
  names(error_func) <- kernels
  # list of prameter setup
  list_param <- list(grad = setGradParam,
                     GD = setGradParam,
                     grid = setGridParam)
  # list of optimizer
  list_optimizer <- list(grad = gradOptimizer_Mix,
                         GD = gradOptimizer_Mix,
                         grid = gridOptimizer_Mix)
  optMethods <- optimizeMethod
  if(length(kernels) != length(optMethods)){
    warning("* kernels and optimization methods differ in sides! Grid search will be used!")
    optMethods = rep("grid", length(kernels))
  }

  # Optimization
  parameters <- map2(.x = kernels,
                     .y = optMethods, 
                     .f = ~ list_optimizer[[.y]](obj_fun = error_func[[.x]],
                                                 setParameter = list_param[[.y]]))
  names(parameters) <- paste0(kernel_real, "_", optMethods)
  return(list(opt_parameters = parameters,
              add_parameters = list(inv_sigma = inv_sigma,
                                    alp = alp,
                                    opt_methods = optimizeMethod),
              basic_machines = mach2))
}

Example.5: We approximate the smoothing parameter of Boston data.


df <- MASS::Boston
train <- logical(nrow(df))
train[sample(length(train), floor(0.75*nrow(df)))] <- TRUE

param <- fit_parameter_Mix(train_input = df[train, 1:13],
                       train_response = df[train, 14],
                       machines = c("knn", "rf", "xgb"),
                       splits = .50,
                       kernels = c("gaussian", "biweight", "triangular"),
                       optimizeMethod = c("grad", "grid", "grid"),
                       setBasicMachineParam = setBasicParameter_Mix(k = 2:6,
                                                                ntree = 1:5*100,
                                                                nrounds_xgb = 1:5*100),
                       setGradParam = setGradParameter_Mix(rate = "linear",
                                                       print_step = FALSE,
                                                       coef_auto = c(0.3, 0.3)),
                       setGridParam = setGridParameter_Mix(min_alpha = 0.0001,
                                                       max_alpha = 3,
                                                       min_beta = 0.0001,
                                                       max_beta = 3))

* Building basic machines ...
    ~ Progress: ... 33% ... 67% ... 100%
 ~ Observed parameter: (alpha, beta) = ( 2.911775e-23 ,  31.59902 ) in 77 itertaions.

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1e-04, 2.79311)

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1e-04, 0.5173241)
param$opt_parameters %>%
  map_dfc(.f = ~ .x$opt_param) %>%
  print

4 Prediction

The smoothing parameter obtained from the previous section can be used to make the final predictions.

4.1 Kernel functions

Several types of kernel functions used for the aggregation are defined in this section.

  • Argument:

    • theta : a 2D-vector of the parameter \((\alpha, \beta)\) used.
    • .y2 : the vector of response variable of the second part \(\mathcal{D}_{\ell}\) of the training data, which is used for the aggregation.
    • .distance : the distance matrix object obtained from dist_matrix function.
    • .kern : the string specifying the kernel function. By default, .kern = "gaussian".
    • .inv_sig, .alp : the parameters of exponential kernel function.
    • .meth : the string of optimization methods to be linked to the name of the kernel functions if any perticular kernels are used more than once (maybe with different optimiztaion method such as “gaussian” kernel, can be used with both “grad” and “grid” optimization methods).
  • Value:

    This function returns the predictions of the aggregation method evaluated with the given parameter.

kernel_pred_Mix <- function(theta,
                        .y2, 
                        .dist1,
                        .dist2,
                        .kern = "gaussian",
                        .inv_sig = sqrt(.5), 
                        .alp = 2,
                        .meth = NA){
  distD <- as.matrix(theta[1]*.dist1+theta[2]*.dist1)
  # Kernel functions
  # ================
  gaussian_kernel <- function(D,
                              .inv_sigma = .inv_sig,
                              .alpha = .alp){
    tem0 <- exp(- D^(.alpha/2)*.inv_sig^.alpha)
    y_hat <- .y2 %*% tem0/colSums(tem0)
    return(t(y_hat))
  }

  # Epanechnikov
  epanechnikov_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] = 0
    y_hat <- .y2 %*% tem0/colSums(tem0)
    return(t(y_hat))
  }
  # Biweight
  biweight_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] = 0
    y_hat <- .y2 %*% tem0^2/colSums(tem0^2)
    y_hat[is.na(y_hat)] <- 0
    return(t(y_hat))
  }

  # Triweight
  triweight_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] = 0
    y_hat <- .y2 %*% tem0^3/colSums(tem0^3)
    y_hat[is.na(y_hat)] <- 0
    return(t(y_hat))
  }

  # Triangular
  triangular_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] <- 0
    y_hat <- .y2 %*% tem0/colSums(tem0)
    y_hat[is.na(y_hat)] = 0
    return(t(y_hat))
 }
  # Naive
  naive_kernel <- function(D){
      tem0 <- (D < 1)
      y_hat <- .y2 %*% tem0/colSums(tem0)
      y_hat[is.na(y_hat)] = 0
      return(t(y_hat))
  }
  # Prediction
  kernel_list <- list(gaussian = gaussian_kernel,
                      epanechnikov = epanechnikov_kernel,
                      biweight = biweight_kernel,
                      triweight = triweight_kernel,
                      triangular = triangular_kernel,
                      naive = naive_kernel)
  res <- tibble(as.vector(kernel_list[[.kern]](D = distD)))
  names(res) <- ifelse(is.na(.meth), 
                       .kern, 
                       paste0(.kern, '_', .meth))
  return(res)
}

4.2 Functions: predict_Mix

  • Argument:

    • fitted_models : the object obtained from fit_parameter_Mix function.
    • new_data : the testing data to be predicted.
    • test_response : the testing response variable, it is optional. If it is given, the mean square error on the testing data is also computed. By default, test_response = NULL.
  • Value:

    This function returns a list of the following objects:

    • fitted_aggregate : the predictions by the aggregation methods.
    • fitted_machine : the predictions given by all the basic machines.
    • mse : the mean square error computed only if the test_reponse argument is note NULL.
# Prediction
predict_Mix <- function(fitted_models,
                        new_data,
                        test_response = NULL){
  opt_param <- fitted_models$opt_parameters
  add_param <- fitted_models$add_parameters
  basic_mach <- fitted_models$basic_machines
  kern0 <- names(opt_param)
  kernel0 <- stringr::str_split(kern0, "_") %>%
    imap_dfc(.f = ~ tibble("{.y}" := .x)) 
  kerns <- kernel0[1,] %>%
    as.character
  opt_meths <- kernel0[2,] %>%
    as.character
  new_data_ <- new_data
  mat_input <- as.matrix(basic_mach$train_data$train_input)
  if(!is.null(basic_mach$train_data$min_input)){
    new_data_ <- scale(new_data, 
                       center = basic_mach$train_data$min_input, 
                       scale = basic_mach$train_data$max_input - basic_mach$train_data$min_input)
  }
  if(is.matrix(new_data_)){
    mat_test <- new_data_
    df_test <- as_tibble(new_data_)
  } else {
    mat_test <- as.matrix(new_data_)
    df_test <- new_data_
  }
  if(is.null(basic_mach$models)){
    pred_test_all <- new_data_
    pred_test0 <- new_data
  }else{
    # Prediction test by basic machines
    built_models <- basic_mach$models
    pred_test <- function(meth){
      if(meth == "knn"){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := FNN::knn.reg(train = mat_input[!basic_mach$id2,], 
                                                            test = mat_test, 
                                                            y = basic_mach$train_data$train_response[!basic_mach$id2],
                                                            k = built_models[[meth]][[k]])$pred)))
      }
      if(meth %in% c("tree", "rf")){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := as.vector(predict(built_models[[meth]][[k]], df_test)))))
      }
      if(meth %in% c("lasso", "ridge")){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := as.vector(predict.glmnet(built_models[[meth]][[k]], mat_test)))))
      }
      if(meth == "xgb"){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := as.vector(predict(built_models[[meth]][[k]], mat_test)))))
      }
      colnames(pre) <- names(built_models[[meth]])
      return(pre)
    }
    pred_test_all <- names(built_models) %>%
      map_dfc(.f = pred_test)
    pred_test0 <- pred_test_all
    if(!is.null(basic_mach$train_data$min_machine)){
        pred_test_all <- scale(pred_test0, 
                               center = basic_mach$train_data$min_machine,
                               scale = basic_mach$train_data$max_machine - basic_mach$train_data$min_machine)
    }
  }
  
  # Prediction train2
  pred_train_all <- basic_mach$fitted_remain
  colnames(pred_test_all) <- colnames(pred_train_all)
  d_train <- dim(pred_train_all)
  d_test <- dim(pred_test_all)
  d_train_input <- dim(mat_input[basic_mach$id2,])
  d_test_input <- dim(new_data_)
  pred_test_mat <- as.matrix(pred_test_all)
  pred_train_mat <- as.matrix(pred_train_all)
  # Distance matrix
  dist_mat <- function(kernel = "gausian"){
    res_1 <- res_2 <- NULL
    if(!(kernel %in% c("naive", "triangular"))){
      res_1 <- 1:d_test_input[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums((mat_input[basic_mach$id2,] - matrix(rep(new_data_[id,], 
                                                                                              d_train_input[1]), 
                                                                                          ncol = d_train_input[2], 
                                                                                          byrow = TRUE))^2)))))
      res_2 <- 1:d_test[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums((pred_train_mat - matrix(rep(pred_test_mat[id,], 
                                                                                              d_train[1]), 
                                                                                          ncol = d_train[2], 
                                                                                          byrow = TRUE))^2)))))
    }
    if(kernel == "triangular"){
      res_1 <- 1:d_test_input[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums(abs(mat_input[basic_mach$id2,] - matrix(rep(new_data_[id,], 
                                                                                        d_train_input[1]), 
                                                                                     ncol = d_train_input[2], 
                                                                                     byrow = TRUE)))))))
      res_2 <- 1:d_test[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums(abs(pred_train_mat - matrix(rep(pred_test_mat[id,], 
                                                                                                 d_train[1]), 
                                                                                             ncol = d_train[2], 
                                                                                             byrow = TRUE)))))))
    }
    if(kernel == "naive"){
      res_1 <- 1:d_test_input[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(apply(abs(mat_input[basic_mach$id2,] - matrix(rep(new_data_[id,], 
                                                                                        d_train_input[1]),
                                                                                     ncol = d_train_input[2], 
                                                                                     byrow = TRUE)), 1, max)))))
      res_2 <- 1:d_test[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(apply(abs(pred_train_mat - matrix(rep(pred_test_mat[id,], d_train[1]), 
                                                                                           ncol = d_train[2], 
                                                                                           byrow = TRUE)), 1, max)))))
    }
    return(list(dist_input = res_1,
                dist_machine = res_2))
  }

  dists <- 1:length(kerns) %>%
      map(.f = ~ dist_mat(kerns[.x]))
  tab_nam <- table(kerns)
  nam <- names(tab_nam[tab_nam > 1])
  vec <- rep(NA, length(kerns))
  for(id in nam){
    id_ <- kerns == id
    if(!is.null(id_)){
      vec[id_] = add_param$opt_methods[id_]
    }
  }

  prediction <- 1:length(kerns) %>% 
    map_dfc(.f = ~ kernel_pred_Mix(theta = opt_param[[kern0[.x]]]$opt_param,
                               .y2 = basic_mach$train_data$train_response[basic_mach$id2],
                               .dist1 = dists[[.x]]$dist_input,
                               .dist2 = dists[[.x]]$dist_machine,
                               .kern = kerns[.x], 
                               .inv_sig = add_param$inv_sigma, 
                               .alp = add_param$alp,
                               .meth = vec[.x]))
  if(is.null(test_response)){
    return(list(fitted_aggregate = prediction,
           fitted_machine = pred_test0))
  } else{
    error <- cbind(pred_test0, prediction) %>%
      dplyr::mutate(y_test = test_response) %>%
      dplyr::summarise_all(.funs = ~ (. - y_test)) %>%
      dplyr::select(-y_test) %>%
      dplyr::summarise_all(.funs = ~ mean(.^2))
    return(list(fitted_aggregate = prediction,
                fitted_machine = pred_test0,
                mse = error))
  }
}

Example.6 Aggregation on Boston dataset.


pred <- predict_Mix(param,
            new_data = df[!train, -14],
            test_response = df[!train, 14])
sqrt(pred$mse)

5 Function : MixCobraReg (direct aggregation)

This function puts together all the functions above and provides the desire result of MixCobra aggregation method.

MixCobraReg <- function(train_input, 
                          train_response,
                          test_input,
                          train_predictions = NULL,
                          test_response = NULL,
                          machines = NULL, 
                          scale_input = TRUE,
                          scale_machine = TRUE,
                          splits = 0.5, 
                          n_cv = 5,
                          inv_sigma = sqrt(.5),
                          alp = 2,
                          kernels = "gaussian",
                          optimizeMethod = "grad",
                          setBasicMachineParam = setBasicParameter_Mix(),
                          setGradParam = setGradParameter_Mix(),
                          setGridParam = setGridParameter_Mix()){
  # build machines + tune parameter
  fit_mod <- fit_parameter_Mix(train_input = train_input, 
                           train_response = train_response,
                           train_predictions = train_predictions,
                           machines = machines, 
                           scale_input = scale_input,
                           scale_machine = scale_machine,
                           splits = splits, 
                           n_cv = n_cv,
                           inv_sigma = inv_sigma,
                           alp = alp,
                           kernels = kernels,
                           optimizeMethod = optimizeMethod,
                           setBasicMachineParam = setBasicMachineParam,
                           setGradParam = setGradParam,
                           setGridParam = setGridParam)
  # prediction
  pred <- predict_Mix(fitted_models = fit_mod,
                      new_data = test_input,
                      test_response = test_response)
  return(list(fitted_aggregate = pred$j,
              fitted_machine = pred$fitted_machine,
              pred_train2 = fit_mod$basic_machines$fitted_remain,
              opt_parameter = fit_mod$opt_parameters,
              mse = pred$mse,
              kernels = kernels,
              ind_D2 = fit_mod$basic_machines$id2))
}

Example.7 A complete aggregation is implemented on Abalone dataset. Three types of basic machines, and three different kernel functions are used.


pacman::p_load(readr)
colname <- c("Type", "LongestShell", "Diameter", "Height", "WholeWeight", "ShuckedWeight", "VisceraWeight", "ShellWeight", "Rings")
df <- readr::read_delim("https://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data", col_names = colname, delim = ",", show_col_types = FALSE)

train <- logical(nrow(df))
train[sample(length(train), floor(0.75*nrow(df)))] <- TRUE

agg <- MixCobraReg(train_input = df[train, 2:8],
                   train_response = df$Rings[train],
                   test_input = df[!train,2:8],
                   test_response = df$Rings[!train],
                   n_cv = 3,
                   machines = c("knn", "rf", "xgb"),
                   splits = .5,
                   kernels = c("gaussian", 
                               "naive", 
                               "triangular"),
                   optimizeMethod = c("grad", 
                                      "grid", 
                                      "grid"),
                   setBasicMachineParam = setBasicParameter_Mix(k = c(2,5,7,10),
                                                              ntree = 2:5*100,
                                                              nrounds_xgb = c(1,3,5)*100),
                   setGradParam = setGradParameter_Mix(rate = "linear",
                                                       coef_auto = c(0.5, 0.5),
                                                       print_step = TRUE,
                                                       print_result = TRUE,
                                                       figure = TRUE),
                   setGridParam = setGridParameter_Mix(parameters = list(alpha = seq(0.0001, 3, length.out = 20),
                                                                     beta = seq(0.0001, 5, length.out = 20))))

* Building basic machines ...
    ~ Progress: ... 33% ... 67% ... 100%
* Gradient descent algorithm ...
  Step  |  alpha    ;  beta     |  Gradient (alpha ; beta)  |  Threshold 
 --------------------------------------------------------------------------------
   0    |  10.00000  ;  50.00000    |  1.187664  ;  -1.1801e-01     |  1e-10 
 --------------------------------------------------------------------------------
   0    |  10.0000  ;  50.0000  |  1.18766  ;  -1.1801e-01  |  11.75103 

   1    |  9.5000  ;  50.5000   |  1.19509  ;  -1.0347e-01  |  0.01666667 

   2    |  8.4937  ;  51.3768   |  1.20438  ;  -8.4406e-02  |  0.03138422 

   3    |  6.9726  ;  52.4497   |  1.19873  ;  -7.8449e-02  |  0.04332695 

   4    |  4.9540  ;  53.7793   |  1.14367  ;  -9.758e-02   |  0.05634606 

   5    |  2.5466  ;  55.8465   |  0.97947  ;  -1.4157e-01  |  0.07618612 

   6    |  0.07249  ;  59.4455  |  0.49258  ;  -1.9466e-01  |  0.2081879 

   7    |  0.03624  ;  65.2190  |  1.11185  ;  0.058122     |  0.5399887 

   8    |  0.01812  ;  63.2488  |  0.89225  ;  -2.4679e-02  |  0.8720525 

   9    |  0.009061  ;  63.7194     |  0.93845  ;  -5.7262e-03  |  0.3024014 

   10   |  0.004531  ;  63.7800     |  0.94255  ;  -3.6737e-03  |  0.06515147 

   11   |  0.002265  ;  63.8228     |  0.94595  ;  -2.1278e-03  |  0.006156164 

   12   |  0.001133  ;  63.8499     |  0.94826  ;  -1.1203e-03  |  0.004945072 

   13   |  0.0005663  ;  63.8653    |  0.94962  ;  -5.3751e-04  |  0.003316515 

   14   |  0.0002832  ;  63.8733    |  0.95032  ;  -2.3565e-04  |  0.001942763 

   15   |  0.0001416  ;  63.8770    |  0.95065  ;  -9.4773e-05  |  0.001009418 

   16   |  7.079e-05  ;  63.8786    |  0.95079  ;  -3.5408e-05  |  0.0004686813 

   17   |  3.54e-05  ;  63.8793     |  0.95084  ;  -1.2466e-05  |  0.0001944641 

   18   |  1.77e-05  ;  63.8795     |  0.95085  ;  -4.5809e-06  |  7.254384e-05 

   19   |  8.849e-06  ;  63.8796    |  0.95086  ;  -1.6521e-06  |  2.391844e-05 

   20   |  4.424e-06  ;  63.8796    |  0.95086  ;  -7.5097e-07  |  8.14804e-06 

   21   |  2.212e-06  ;  63.8797    |  0.95086  ;  -7.5097e-08  |  2.252914e-06 

   22   |  1.106e-06  ;  63.8797    |  0.95086  ;  -1.5019e-07  |  1.389297e-06 

   23   |  5.531e-07  ;  63.8797    |  0.95086  ;  -7.5097e-08  |  5.2568e-07 

   24   |  2.765e-07  ;  63.8797    |  0.95086  ;  0e+00    |  1.126457e-07 

   25   |  1.383e-07  ;  63.8797    |  0.95086  ;  -7.5097e-08  |  2.252914e-07 

   26   |  6.913e-08  ;  63.8797    |  0.95086  ;  -2.2529e-07  |  2.252914e-07 

   27   |  3.457e-08  ;  63.8797    |  0.95086  ;  -3.7549e-08  |  2.6284e-07 

   28   |  1.728e-08  ;  63.8797    |  0.95086  ;  -1.1265e-07  |  1.877429e-07 

   29   |  8.641e-09  ;  63.8797    |  0.95086  ;  -7.5097e-08  |  2.6284e-07 

   30   |  4.321e-09  ;  63.8797    |  0.95086  ;  -3.7549e-08  |  1.126457e-07 

   31   |  2.16e-09  ;  63.8797     |  0.95086  ;  0e+00    |  7.509715e-08 

   32   |  1.08e-09  ;  63.8797     |  0.95086  ;  0e+00    |  3.754857e-08 

   33   |  5.401e-10  ;  63.8797    |  0.95086  ;  0e+00    |  3.754857e-08 

   34   |  2.7e-10  ;  63.8797  |  0.95086  ;  1.5019e-07   |  1.501943e-07 

   35   |  1.35e-10  ;  63.8797     |  0.95086  ;  0e+00    |  3.003886e-07 

   36   |  6.751e-11  ;  63.8797    |  0.95086  ;  3.7549e-08   |  2.6284e-07 

   37   |  3.376e-11  ;  63.8797    |  0.95086  ;  -7.5097e-08  |  7.509715e-08 

   38   |  1.688e-11  ;  63.8797    |  0.95086  ;  3.7549e-08   |  1.877429e-07 

   39   |  8.439e-12  ;  63.8797    |  0.95086  ;  0e+00    |  1.877429e-07 

   40   |  4.219e-12  ;  63.8797    |  0.95086  ;  0e+00    |  1.501943e-07 

   41   |  2.11e-12  ;  63.8797     |  0.95086  ;  -7.5097e-08  |  7.509715e-08 

   42   |  1.055e-12  ;  63.8797    |  0.95086  ;  -3.7549e-08  |  1.501943e-07 

   43   |  5.274e-13  ;  63.8797    |  0.95086  ;  0e+00    |  1.877429e-07 

   44   |  2.637e-13  ;  63.8797    |  0.95086  ;  0e+00    |  3.754857e-08 

   45   |  1.319e-13  ;  63.8797    |  0.95086  ;  0e+00    |  3.754857e-08 

   46   |  6.593e-14  ;  63.8797    |  0.95086  ;  0e+00    |  7.509715e-08 
--------------------------------------------------------------------------------
 Stopped|  3.296e-14  ;  63.8797    |  0.95086  ;  0e+00    |  5.160414e-16
 ~ Observed parameter: (alpha, beta) = ( 3.296455e-14 ,  63.87967 ) in 47 itertaions.

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1.105326, 4.473695)
Warning in eval(f) : restarting interrupted promise evaluation

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (0.1579895, 0.7895579)
sqrt(agg$mse)

References



LS0tDQp0aXRsZTogIjxzcGFuIHN0eWxlPSdjb2xvcjogIzFDODFBQTsnPioqTWl4Q29icmEqKiAtPC9zcGFuPiBbRmlzY2hlciBhbmQgTW91Z2VvdCAoMjAxOSldKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAzNzgzNzU4MTgzMDIzNDkpIg0KYXV0aG9yOiAiU290aGVhIEhhcyINCmRhdGU6ICI1LzIvMjAyMiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjc3M6IGhpZGVPdXRwdXQuY3NzDQogICAgaW5jbHVkZXM6DQogICAgICBpbl9oZWFkZXI6IGhpZGVPdXRwdXQuc2NyaXB0DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogJzInDQogICAgdG9jZGVwdGg6IDINCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjc3M6IGhpZGVPdXRwdXQuY3NzDQogICAgaW5jbHVkZXM6DQogICAgICBpbl9oZWFkZXI6IGhpZGVPdXRwdXQuc2NyaXB0DQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMg0KICAgIHRvY2RlcHRoOiAyDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogJzInDQotLS0NCg0KPHN0eWxlPg0KICAuYnRuIHsNCiAgICBib3JkZXItd2lkdGg6IDAgMHB4IDBweCAwcHg7DQogICAgZm9udC13ZWlnaHQ6IG5vcm1hbDsNCiAgICB0ZXh0LXRyYW5zZm9ybTogOw0KICB9DQouYnRuLWRlZmF1bHQgew0KICBjb2xvcjogIzJlY2M3MTsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmZmZmOw0KICAgIGJvcmRlci1jb2xvcjogI2ZmZmZmZjsNCn0NCjwvc3R5bGU+DQoNCjwhLS0gQ29sb3JzDQpibHVlIDogIzFGQUFFMw0KeWVsbG93IDogI0YwQUUxNA0KZ3JlZW4gOiAjNTREMzE5IA0KcmVkIDogI0U2MTgwQQ0KLS0+DQoNCg0KYGBge3IsIGVjaG89RkFMU0V9DQojIENoZWNrIGlmIHBhY2thZ2UgImZvbnRhd2Vzb21lIiBpcyBhbHJlYWR5IGluc3RhbGxlZCANCg0KbG9va3VwX3BhY2thZ2VzIDwtIGluc3RhbGxlZC5wYWNrYWdlcygpWywxXQ0KaWYoISgiZm9udGF3ZXNvbWUiICVpbiUgbG9va3VwX3BhY2thZ2VzKSkNCiAgaW5zdGFsbC5wYWNrYWdlcygiZm9udGF3ZXNvbWUiKQ0KYGBgDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPiYjMTI4MjcwOzx1PiBIb3cgdG8gZG93bmxvYWQgJiBydW4gdGhlIGNvZGVzPzwvdT48L3NwYW4+ey19DQo9PT0NCg0KQWxsIHRoZSBzb3VyY2UgY29kZXMgb2YgdGhlIGFnZ3JlZ2F0aW9uIG1ldGhvZHMgYXJlIGF2YWlsYWJsZSBbaGVyZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj5dKGh0dHBzOi8vZ2l0aHViLmNvbS9oYXNzb3RoZWEvQWdncmVnYXRpb25NZXRob2RzKS4gVG8gcnVuIHRoZSBjb2RlcywgeW91IGNhbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBjbG9uZWA8L3NwYW4+IHRoZSByZXBvc2l0b3J5IGRpcmVjdGx5IG9yIHNpbXBseSBsb2FkIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBSIHNjcmlwdGA8L3NwYW4+IHNvdXJjZSBmaWxlIGZyb20gdGhlIHJlcG9zaXRvcnkgdXNpbmcgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSBpbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwMjg3RDg7Ij4gKipSc3R1ZGlvKiogPC9zcGFuPiBhcyBmb2xsb3c6DQoNCjEuIEluc3RhbGwgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSB1c2luZyBjb21tYW5kOiANCg0KICAgIGBpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpYA0KDQoyLiBMb2FkaW5nIHRoZSBzb3VyY2UgY29kZXMgZnJvbSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPkdpdEh1YiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj4gcmVwb3NpdG9yeSB1c2luZyBgc291cmNlX3VybGAgZnVuY3Rpb24gYnk6IA0KDQogICAgYGRldnRvb2xzOjpzb3VyY2VfdXJsKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vaGFzc290aGVhL0FnZ3JlZ2F0aW9uTWV0aG9kcy9tYWluL01peENvYnJhUmVnLlIiKWANCg0KLS0tDQoNCj4qKiYjOTk5ODsgTm90ZSoqOiBBbGwgY29kZXMgY29udGFpbmVkIGluIHRoaXMgYFJtYXJrZG93bmAgYXJlIGJ1aWx0IHdpdGggcmVjZW50IHZlcnNpb24gb2YgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxOyI+YHIgZm9udGF3ZXNvbWU6OmZhKCJyLXByb2plY3QiKWA8L3NwYW4+ICh2ZXJzaW9uICQ+JCA0LjEsIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvYmluL3dpbmRvd3MvYmFzZS8pKSBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDI4N0Q4OyI+ICoqUnN0dWRpbyoqIDwvc3Bhbj4gKHZlcnNpb24gPiBgMjAyMi4wMi4yKzQ4NWAsIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcHJvZHVjdHMvcnN0dWRpby9kb3dubG9hZC8jZG93bmxvYWQpKS4gTm90ZSBhbHNvIHRoYXQgdGhlIGNvZGUgY2h1Y2tzIGFyZSA8c3BhbiBzdHlsZT0iY29sb3I6ICNFNjE4MEE7Ij5oaWRkZW48L3NwYW4+IGJ5IGRlZmF1bHQuDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNCI+ICoqVG8gc2VlIHRoZSBjb2RlcywgeW91IGNhbjoqKiA8L3NwYW4+DQoNCi0gY2xpY2sgb24gdGhlIHRvcC1yaWdodCA8c3BhbiBzdHlsZT0iY29sb3I6ICM1NEQzMTkgOyI+YENvZGVgPC9zcGFuPiBidXR0b24gb2YgdGhlIHBhZ2UsIHRoZW4gY2hvb3NlICoqU2hvdyBBbGwgQ29kZSoqIHRvIHNob3cgYWxsIHRoZSBjb2Rlcywgb3IgDQotIHNpbXBseSBjbGljayBvbiB0aGUgcmlnaHQtY29ybmVyIDxzcGFuIHN0eWxlPSJjb2xvcjogIzU0RDMxOSA7Ij5gQ29kZWA8L3NwYW4+IGJ1dHRvbiBhdCBlYWNoIHNlY3Rpb24gdG8gc2hvdyB0aGUgY29kZXMgb2YgdGhhdCBzcGVjaWZpYyBzZWN0aW9uLg0KDQotLS0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+IE1peENvYnJhICYgaW1wb3J0YW50IHBhY2thZ2VzIDwvdT48L3NwYW4+DQo9PT0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+IE1peENvYnJhIG1ldGhvZDwvdT48L3NwYW4+DQotLS0NCg0KVGhpcyBgUm1hcmtkb3duYCBwcm92aWRlcyB0aGUgaW1wbGVtZW50YXRpb24gb2YgYW4gYWdncmVnYXRpb24gbWV0aG9kIHVzaW5nIGlucHV0IGFuZCBvdXQgdHJhZGUtb2ZmIGJ5IDxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPltGaXNjaGVyIGFuZCBNb3VnZW90ICgyMDE5KV0oaHR0cHM6Ly93d3cuc2NpZW5jZWRpcmVjdC5jb20vc2NpZW5jZS9hcnRpY2xlL3BpaS9TMDM3ODM3NTgxODMwMjM0OSk8L3NwYW4+Lg0KTGV0ICRcbWF0aGNhbHtEfV9uPVx7KHhfMSx5XzEpLC4uLiwoeF9uLHlfbilcfSQgYmUgYSB0cmFpbmluZyBkYXRhIG9mIHNpemUgJG4kLCB3aGVyZSB0aGUgaW5wdXQtb3V0cHV0IGNvdXBsZXMgJCh4X2kseV9pKVxpblxtYXRoYmJ7Un1eZFx0aW1lc1xtYXRoYmJ7Un0kIGZvciBhbGwgJGk9MSwuLi4sbiQuICRcbWF0aGNhbHtEfV97bn0kIGlzIGZpcnN0IHJhbmRvbWx5IHBhcnRpdGlvbmVkIGludG8gJFxtYXRoY2Fse0R9X3trfSQgYW5kICRcbWF0aGNhbHtEfV97XGVsbH0kIG9mIHNpemUgJGskIGFuZCAkXGVsbCQgcmVzcGVjdGl2ZWx5IHN1Y2ggdGhhdCAkaytcZWxsPW4kLiBXZSBjb25zdHJ1Y3QgJE0kIHJlZ3Jlc3Npb24gZXN0aW1hdG9ycyAobWFjaGluZXMpICAkcl8xLC4uLixyX00kIHVzaW5nIG9ubHkgJFxtYXRoY2Fse0R9X3trfSQuIExldCAke1xiZiByfSh4KT0ocl8xKHgpLC4uLixyX00oeCkpXlRcaW5cbWF0aGJie1J9Xk0kIGJlIHRoZSB2ZWN0b3Igb2YgcHJlZGljdGlvbnMgb2YgJHhcaW5cbWF0aGJie1J9XmQkLCB0aGUga2VybmVsLWJhc2VkIGNvbnNlbnN1YWwgYWdncmVnYXRpb24gbWV0aG9kIGV2YWx1YXRlZCBhdCBwb2ludCAkeCQgaXMgZGVmaW5lZCBieQ0KDQpcYmVnaW57ZXF1YXRpb259DQpnX24oeCk9XGZyYWN7XHN1bV97aT0xfV57XGVsbH15X2lLX3tcYWxwaGEsXGJldGF9KHgteF9pLHtcYmYgcn0oeCkte1xiZiByfSh4X2kpKX17XHN1bV97aj0xfV57XGVsbH1LX3tcYWxwaGEsXGJldGF9KHgteF9qLHtcYmYgcn0oeCkte1xiZiByfSh4X2opKX0NClxlbmR7ZXF1YXRpb259DQp3aGVyZSAkSzpcbWF0aGJie1J9XntkK019XHRvXG1hdGhiYntSfV8rJCBpcyBhIG5vbi1pbmNyZWFzaW5nIGtlcm5lbCBmdW5jdGlvbiB3aXRoICRLX3tcYWxwaGEsXGJldGF9KHUsdik9SyhcZnJhY3t1fXtcYWxwaGF9LFxmcmFje3Z9e1xiZXRhfSkkIGZvciBzb21lIHNtb290aGluZyBwYXJhbWV0ZXIgJFxhbHBoYSxcYmV0YT4wJCB0byBiZSB0dW5lZCwgd2l0aCB0aGUgY29udmVudGlvbiAkMC8wPTAkLiANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij4gPHU+IEltcG9ydGFudCBwYWNrYWdlczwvdT48L3NwYW4+DQotLS0NCg0KV2UgcHJlcGFyZSBhbGwgdGhlIG5lY2Vzc2FyeSB0b29scyBmb3IgdGhpcyBgUm1hcmtkb3duYC4gYHBhY21hbmAgcGFja2FnZSBhbGxvd3MgdXMgdG8gbG9hZCAoaWYgZXhpc3RzKSBvciBpbnN0YWxsIChpZiBkb2VzIG5vdCBleGlzdCkgYW55IGF2YWlsYWJsZSBwYWNrYWdlcyBmcm9tIFtUaGUgQ29tcHJlaGVuc2l2ZSBSIEFyY2hpdmUgTmV0d29yayAoQ1JBTildKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnLykgb2YgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj5gciBmb250YXdlc29tZTo6ZmEoInItcHJvamVjdCIpYDwvc3Bhbj4uIA0KDQoNCmBgYHtyfQ0KIyBDaGVjayBpZiBwYWNrYWdlICJwYWNtYW4iIGlzIGFscmVhZHkgaW5zdGFsbGVkIA0KDQpsb29rdXBfcGFja2FnZXMgPC0gaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdDQppZighKCJwYWNtYW4iICVpbiUgbG9va3VwX3BhY2thZ2VzKSl7DQogIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQp9DQoNCiMgVG8gYmUgaW5zdGFsbGVkIG9yIGxvYWRlZA0KcGFjbWFuOjpwX2xvYWQobWFncml0dHIpDQpwYWNtYW46OnBfbG9hZChnZ3Bsb3QyKQ0KcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlKQ0KDQojIyBwYWNrYWdlIGZvciAiZ2VuZXJhdGVNYWNoaW5lcyINCnBhY21hbjo6cF9sb2FkKHRyZWUpDQpwYWNtYW46OnBfbG9hZChnbG1uZXQpDQpwYWNtYW46OnBfbG9hZChyYW5kb21Gb3Jlc3QpDQpwYWNtYW46OnBfbG9hZChGTk4pDQpwYWNtYW46OnBfbG9hZCh4Z2Jvb3N0KQ0KcGFjbWFuOjpwX2xvYWQoa2VyYXMpDQpwYWNtYW46OnBfbG9hZChwcmFjbWEpDQpwYWNtYW46OnBfbG9hZChsYXRleDJleHApDQpwYWNtYW46OnBfbG9hZChwbG90bHkpDQpwYWNtYW46OnBfbG9hZChkYXNoKQ0Kcm0obG9va3VwX3BhY2thZ2VzKQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+QmFzaWMgTWFjaGluZSBnZW5lcmF0b3I8L3U+PC9zcGFuPg0KPT09DQoNClRoaXMgc2VjdGlvbiBwcm92aWRlcyBmdW5jdGlvbnMgdG8gZ2VuZXJhdGUgYmFzaWMgbWFjaGluZXMgKHJlZ3Jlc3NvcnMpIHRvIGJlIGFnZ3JlZ2F0ZWQuDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBgc2V0QmFzaWNQYXJhbWV0ZXJfTWl4YA0KLS0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBzZXQgdGhlIHZhbHVlcyBvZiBzb21lIGtleSBwYXJhbWV0ZXJzIG9mIHRoZSBiYXNpYyBtYWNoaW5lcy4NCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBsYW1iZGFgIDogdGhlIHBlbmFsdHkgcGFyYW1ldGVyICRcbGFtYmRhJCB1c2VkIGluIHBlbmFsaXplZCBsaW5lYXIgbW9kZWxzOiBgcmlkZ2VgIG9yIGBsYXNzb2AuDQogICAgLSBga2AgOiB0aGUgcGFyYW1ldGVyICRrJCBvZiAkayROTiAoYGtubmApIHJlZ3Jlc3Npb24gbW9kZWwgYW5kIHRoZSBkZWZhdWx0IHZhbHVlIGlzICRrPTEwJC4NCiAgICAtIGBudHJlZWAgOiB0aGUgbnVtYmVyIG9mIHRyZWVzIGluIHJhbmRvbSBmb3Jlc3QgKGByZmApLiBCeSBkZWZhdWx0LCBgbnRyZWUgPSAzMDBgLg0KICAgIC0gYG10cnlgIDogdGhlIG51bWJlciBvZiByYW5kb20gZmVhdHVyZXMgY2hvc2VuIGluIGVhY2ggc3BsaXQgb2YgcmFuZG9tIGZvcmVzdCBwcm9jZWR1cmUuIEJ5IGRlZmF1bHQsIGBtdHJ5ID0gTlVMTGAgYW5kIHRoZSBkZWZhdWx0IHZhbHVlIG9mIGBtdHJ5YCBvZiAqcmFuZG9tRm9yZXN0KiBmdW5jdGlvbiBmcm9tIFtyYW5kb21Gb3Jlc3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yYW5kb21Gb3Jlc3QvaW5kZXguaHRtbCkgbGlicmFyeSBpcyB1c2VkLg0KICAgIC0gYGV0YV94Z2JgIDogdGhlIGxlYXJuaW5nIHJhdGUgJFxldGE+MCQgaW4gZ3JhZGllbnQgc3RlcCBvZiAqZXh0cmVtZSBncmFkaWVudCBib29zdGluZyogbWV0aG9kIChgeGdiYCkgb2YgW3hnYm9vc3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy94Z2Jvb3N0L2luZGV4Lmh0bWwpIGxpYnJhcnkuDQogICAgLSBgbnJvdW5kc194Z2JgIDogdGhlIHBhcmFtZXRlciBgbnJvdW5kc2AgaW5kaWNhdGluZyB0aGUgbWF4IG51bWJlciBvZiBib29zdGluZyBpdGVyYXRpb25zLiBCeSBkZWZhdWx0LCBgbnJvdW5kc194Z2IgPSAxMDBgLg0KICAgIC0gYGVhcmx5X3N0b3BfeGdiYCA6IHRoZSBlYXJseSBzdG9wcGluZyByb3VuZCBjcml0ZXJpb24gb2YgYHhnYm9vc3RgIGZ1bmN0aW9uLiBCeSwgZGVmYXVsdCwgYGVhcmx5X3N0b3BfeGdiID0gTlVMTGAgYW5kIHRoZSBlYXJseSBzdG9wcGluZyBmdW5jdGlvbiBpcyBub3QgdHJpZ2dlcmVkLg0KICAgIC0gYG1heF9kZXB0aF94Z2JgIDogbWF4aW11bSBkZXB0aCBvZiB0cmVlcyBjb25zdHJ1Y3RlZCBpbiBgeGdib29zdGAuIA0KDQotICoqVmFsdWUqKjogDQogICAgDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKmxpc3QqIG9mIGFsbCB0aGUgcGFyYW1ldGVycyBnaXZlbiBpbiBpdHMgYXJndW1lbnRzLCB0byBiZSBmZWQgdG8gdGhlIGBiYXNpY01hY2hpbmVQYXJhbWAgYXJndW1lbnQgb2YgZnVuY3Rpb24gYGdlbmVyYXRlTWFjaGluZXNfTWl4YCBkZWZpbmVkIGluIHRoZSBuZXh0IHNlY3Rpb24uDQoNCi0tLQ0KDQo+KipSZW1hcmsuMSoqOiANCmBsYW1iZGFgLCBga2AsIGBudHJlZWAgY2FuIGJlIGEgc2luZ2xlIHZhbHVlIG9yIGEgdmVjdG9yLiBJbiBvdGhlciB3b3JkcywgZWFjaCB0eXBlIG9mIG1vZGVscyBjYW4gYmUgY29uc3RydWN0ZWQgc2V2ZXJhbCB0aW1lcyBhY2NvcmRpbmcgdG8gdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVycyAkKFxhbHBoYSwgXGJldGEpJCBvZiB0aGUgbWV0aG9kLg0KDQotLS0NCg0KYGBge3J9DQpzZXRCYXNpY1BhcmFtZXRlcl9NaXggPC0gZnVuY3Rpb24obGFtYmRhID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGsgPSA1LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gMzAwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSBOVUxMLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV0YV94Z2IgPSAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHNfeGdiID0gMTAwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVhcmx5X3N0b3BfeGdiID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9kZXB0aF94Z2IgPSAzKXsNCiAgcmV0dXJuKGxpc3QoDQogICAgbGFtYmRhID0gbGFtYmRhLA0KICAgIGsgPSBrLA0KICAgIG50cmVlID0gbnRyZWUsIA0KICAgIG10cnkgPSBtdHJ5LCANCiAgICBldGFfeGdiID0gZXRhX3hnYiwgDQogICAgbnJvdW5kc194Z2IgPSBucm91bmRzX3hnYiwgDQogICAgZWFybHlfc3RvcF94Z2IgPSBlYXJseV9zdG9wX3hnYiwNCiAgICBtYXhfZGVwdGhfeGdiID0gbWF4X2RlcHRoX3hnYikNCiAgKQ0KfQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBnZW5lcmF0ZU1hY2hpbmVzX01peGANCi0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGdlbmVyYXRlcyBhbGwgdGhlIGJhc2ljIG1hY2hpbmVzIHRvIGJlIGFnZ3JlZ2F0ZWQuIA0KDQotICoqQXJndW1lbnQqKjoNCg0KICAgIC0gYHRyYWluX2lucHV0YCA6IGEgbWF0cml4IG9yIGRhdGEgZnJhbWUgb2YgdGhlIHRyYWluaW5nIGlucHV0IGRhdGEuDQogICAgLSBgdHJhaW5fcmVzcG9uc2VgIDogYSB2ZWN0b3Igb2YgdHJhaW5pbmcgcmVzcG9uc2UgdmFyaWFibGUgY29ycmVzcG9uZGluZyB0byB0aGUgYHRyYWluX2lucHV0YC4NCiAgICAtIGBzY2FsZV9pbnB1dGAgOiBsb2dpY2FsIHZhbHVlIHNwZWNpZnlpbmcgd2hldGhlciB0byBzY2FsZSB0aGUgaW5wdXQgZGF0YSAodG8gYmUgYmV0d2VlbiAkMCQgYW5kICQxJCkgb3Igbm90LiBCeSBkZWZhdWx0LCBgc2NhbGVfaW5wdXQgPSBUUlVFYC4NCiAgICAtIGBzY2FsZV9tYWNoaW5lYCA6IGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIHRvIHNjYWxlIHRoZSBwcmVkaWN0aW9ucyBvZiB0aGUgcmVtYWluaW5nIHBhcnQgJFxtYXRoY2Fse0R9X3tcZWxsfSQgb2YgdGhlIHRyYWluaW5nIGRhdGEgKHRvIGJlIGJldHdlZW4gJDAkIGFuZCAkMSQpIG9yIG5vdC4gQnkgZGVmYXVsdCwgYHNjYWxlX21hY2hpbmUgPSBUUlVFYC4NCiAgICAtIGBtYWNoaW5lc2AgOiB0eXBlcyBvZiBiYXNpYyBtYWNoaW5lcyB0byBiZSBjb25zdHJ1Y3RlZC4gSXQgaXMgYSBzdWJzZXQgb2YgeyJsYXNzbyIsICJyaWRnZSIsICJrbm4iLCAidHJlZSIsICJyZiIsICJ4Z2IifS4gQnkgZGVmYXVsdCwgYG1hY2hpbmVzID0gTlVMTGAgYW5kIGFsbCB0aGUgc2l4IHR5cGVzIG9mIGJhc2ljIG1hY2hpbmVzIGFyZSBidWlsdC4NCiAgICAtIGBzcGxpdHNgIDogcmVhbCBudW1iZXIgYmV0d2VlbiAkMCQgYW5kICQxJCBzcGVjaWZ5aW5nIHRoZSBwcm9wb3J0aW9uIG9mIHRyYWluaW5nIGRhdGEgdXNlZCB0byB0cmFpbiB0aGUgYmFzaWMgbWFjaGluZXMgKCRcbWF0aGNhbHtEfV9rJCkuIFRoZSByZW1haW5pbmcgcHJvcG9ydGlvbiBvZiAoJDEtJCBgc3BsaXRzYCkgaXMgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uICgkXG1hdGhjYWx7RH1fe1xlbGx9JCkuIEJ5IGRlZmF1bHQsIGBzcGxpdHMgPSAwLjVgLg0KICAgIC0gYGJhc2ljTWFjaGluZVBhcmFtYCA6IHRoZSBvcHRpb24gdXNlZCB0byBzZXR1cCB0aGUgdmFsdWVzIG9mIHBhcmFtZXRlcnMgb2YgZWFjaCBtYWNoaW5lcy4gT25lIHNob3VsZCBmZWVkIHRoZSBmdW5jdGlvbiBgc2V0QmFzaWNQYXJhbWV0ZXJfTWl4KClgIGRlZmluZWQgYWJvdmUgdG8gdGhpcyBhcmd1bWVudC4NCiAgICANCiAgICANCi0gKipWYWx1ZSoqOiANCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzLg0KDQogICAgLSBgZml0dGVkX3JlbWFpbmAgOiB0aGUgcHJlZGljdGlvbnMgb2YgdGhlIHJlbWFpbmluZyBwYXJ0ICgkXG1hdGhjYWx7RH1fe1xlbGx9JCkgb2YgdGhlIHRyYWluaW5nIGRhdGEgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLg0KICAgIC0gYG1vZGVsc2AgOiBhbGwgdGhlIGNvbnN0cnVjdGVkIGJhc2ljIG1hY2hpbmVzIChpdCBjb250YWlucyBvbmx5IHRoZSB2YWx1ZXMgb2YgcHJhcGV0ZXIgJGskIGZvciBga25uYCkuDQogICAgLSBgaWQyYCA6IGEgbG9naWNhbCB2ZWN0b3Igb2Ygc2l6ZSBlcXVhbHMgdG8gdGhlIG51bWJlciBvZiBsaW5lcyBvZiB0aGUgdHJhaW5pbmcgZGF0YSBpbmRpY2F0aW5nIHRoZSBsb2NhdGlvbiBvZiB0aGUgcG9pbnRzIHVzZWQgdG8gYnVpbGQgdGhlIGJhc2ljIG1hY2hpbmVzIChgRkFMU0VgKSBhbmQgdGhlIHJlbWFpbmluZyBvbmVzIChgVFJVRWApLg0KICAgIC0gYHRyYWluX2RhdGFgIDogYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICAgICAgLSBgdHJhaW5faW5wdXRgIDogdHJhaW5pbmcgaW5wdXQgZGF0YSAoc2NhbGUgb3Igbm9uLXNjYWxpbmcgYWNjb3JkaW5nbHkpLg0KICAgICAgICAtIGBwcmVkaWN0X3JlbWFpbl9vcmdgIDogcHJlZGljdGlvbnMgb2YgdGhlIHNlY29uZCBwYXJ0ICR7XGNhbCBEfV97XGVsbH0kIG9mIHRoZSB0cmFpbmluZyBkYXRhIHdpdGhvdXQgc2NhbGluZy4NCiAgICAgICAgLSBgdHJhaW5fcmVzcG9uc2VgIDogdGhlIHRyYWluZ2luZyByZXNwb25zZSB2YXJpYWJsZS4NCiAgICAgICAgLSBgbWluX21hY2hpbmVgLCBgbWF4X21hY2hpbmVgIDogdmVjdG9ycyBvZiBtaW5pbXVtIGFuZCBtYXhpbXVtIHZhbHVlcyBwcmVkaWN0ZWQgdmFsdWVzIG9mIHRoZSByZW1haW5pbmcgcGFydCAkXG1hdGhjYWx7RH1fe1xlbGx9JCBvZiB0aGUgdHJhaW5pbmcgZGF0YS4gVGhleSBhcmUgYE5VTExgIGlmIGBzY2FsZV9tYWNoaW5lID0gRkFMU0VgLg0KICAgICAgICAtIGBtaW5faW5wdXRgLCBgbWF4X2lucHV0YCA6IHZlY3RvcnMgb2YgbWluaW11bSBhbmQgbWF4aW11bSB2YWx1ZXMgb2YgZWFjaCB2YXJpYWJsZSBvZiB0aGUgdHJhaW5pbmcgaW5wdXQuIFRoZXkgYXJlIGBOVUxMYCBpZiBgc2NhbGVfaW5wdXQgPSBGQUxTRWAuDQogICAgDQotLS0NCg0KPiAqKiYjOTk5ODsgTm90ZSoqOiAqWW91IG1heSBuZWVkIHRvIG1vZGlmeSB0aGUgZnVuY3Rpb24gYWNjb3JkaW5nbHkgaWYgeW91IHdhbnQgdG8gYnVpbGQgZGlmZmVyZW50IHR5cGVzIG9mIGJhc2ljIG1hY2hpbmVzKi4NCg0KLS0tDQoNCmBgYHtyfQ0KZ2VuZXJhdGVNYWNoaW5lc19NaXggPC0gZnVuY3Rpb24odHJhaW5faW5wdXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9tYWNoaW5lID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFjaGluZXMgPSBOVUxMLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gMC41LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY1BhcmFtZXRlcl9NaXgoKSl7DQogIGxhbWJkYSA9IGJhc2ljTWFjaGluZVBhcmFtJGxhbWJkYQ0KICBrIDwtIGJhc2ljTWFjaGluZVBhcmFtJGsgDQogIG50cmVlIDwtIGJhc2ljTWFjaGluZVBhcmFtJG50cmVlIA0KICBtdHJ5IDwtIGJhc2ljTWFjaGluZVBhcmFtJG10cnkNCiAgZXRhX3hnYiA8LSBiYXNpY01hY2hpbmVQYXJhbSRldGFfeGdiIA0KICBucm91bmRzX3hnYiA8LSBiYXNpY01hY2hpbmVQYXJhbSRucm91bmRzX3hnYg0KICBlYXJseV9zdG9wX3hnYiA8LSBiYXNpY01hY2hpbmVQYXJhbSRlYXJseV9zdG9wX3hnYg0KICBtYXhfZGVwdGhfeGdiIDwtIGJhc2ljTWFjaGluZVBhcmFtJG1heF9kZXB0aF94Z2INCiAgDQogICMgUGFja2FnZXMNCiAgcGFjbWFuOjpwX2xvYWQodHJlZSkNCiAgcGFjbWFuOjpwX2xvYWQoZ2xtbmV0KQ0KICBwYWNtYW46OnBfbG9hZChyYW5kb21Gb3Jlc3QpDQogIHBhY21hbjo6cF9sb2FkKEZOTikNCiAgcGFjbWFuOjpwX2xvYWQoeGdib29zdCkNCiAgIyBwYWNtYW46OnBfbG9hZChrZXJhcykNCiAgDQogICMgUHJlcGFyaW5nIGRhdGENCiAgaW5wdXRfbmFtZXMgPC0gY29sbmFtZXModHJhaW5faW5wdXQpDQogIGlucHV0X3NpemUgPC0gZGltKHRyYWluX2lucHV0KQ0KICBkZl9pbnB1dCA8LSB0cmFpbl9pbnB1dF9zY2FsZSA8LSB0cmFpbl9pbnB1dA0KICBtYXhzIDwtIG1pbnMgPC0gTlVMTA0KICBpZihzY2FsZV9pbnB1dCl7DQogICAgbWF4cyA8LSBtYXBfZGJsKC54ID0gZGZfaW5wdXQsIC5mID0gbWF4KQ0KICAgIG1pbnMgPC0gbWFwX2RibCgueCA9IGRmX2lucHV0LCAuZiA9IG1pbikNCiAgICB0cmFpbl9pbnB1dF9zY2FsZSA8LSBzY2FsZSh0cmFpbl9pbnB1dCwgY2VudGVyID0gbWlucywgc2NhbGUgPSBtYXhzIC0gbWlucykNCiAgfQ0KICBpZihpcy5tYXRyaXgodHJhaW5faW5wdXRfc2NhbGUpKXsNCiAgICBkZl9pbnB1dCA8LSBhc190aWJibGUodHJhaW5faW5wdXRfc2NhbGUpDQogICAgbWF0cml4X2lucHV0IDwtIHRyYWluX2lucHV0X3NjYWxlDQogIH0gZWxzZXsNCiAgICBkZl9pbnB1dCA8LSB0cmFpbl9pbnB1dF9zY2FsZQ0KICAgIG1hdHJpeF9pbnB1dCA8LSBhcy5tYXRyaXgodHJhaW5faW5wdXRfc2NhbGUpDQogIH0NCiAgDQogICMgTWFjaGluZXMNCiAgbGFzc29fbWFjaGluZSA8LSBmdW5jdGlvbih4LCBsYW1iZGEwKXsNCiAgICBpZihpcy5udWxsKGxhbWJkYSkpew0KICAgICAgY3YgPC0gY3YuZ2xtbmV0KG1hdHJpeF90cmFpbl94MSwgdHJhaW5feTEsIGFscGhhID0gMSwgbGFtYmRhID0gMTBeKHNlcSgtMywyLGxlbmd0aC5vdXQgPSA1MCkpKQ0KICAgICAgbW9kIDwtIGdsbW5ldChtYXRyaXhfdHJhaW5feDEsIHRyYWluX3kxLCBhbHBoYSA9IDEsIGxhbWJkYSA9IGN2JGxhbWJkYS5taW4pDQogICAgfSBlbHNlew0KICAgICAgbW9kIDwtIGdsbW5ldChtYXRyaXhfdHJhaW5feDEsIHRyYWluX3kxLCBhbHBoYSA9IDEsIGxhbWJkYSA9IGxhbWJkYTApDQogICAgfQ0KICAgIHJlcyA8LSBwcmVkaWN0LmdsbW5ldChtb2QsIG5ld3ggPSB4KQ0KICAgIHJldHVybihsaXN0KHByZWQgPSByZXMsDQogICAgICAgICAgICAgICAgbW9kZWwgPSBtb2QpKQ0KICB9DQogIHJpZGdlX21hY2hpbmUgPC0gZnVuY3Rpb24oeCwgbGFtYmRhMCl7DQogICAgaWYoaXMubnVsbChsYW1iZGEpKXsNCiAgICAgIGN2IDwtIGN2LmdsbW5ldChtYXRyaXhfdHJhaW5feDEsIHRyYWluX3kxLCBhbHBoYSA9IDAsIGxhbWJkYSA9IDEwXihzZXEoLTMsMixsZW5ndGgub3V0ID0gNTApKSkNCiAgICAgIG1vZCA8LSBnbG1uZXQobWF0cml4X3RyYWluX3gxLCB0cmFpbl95MSwgYWxwaGEgPSAwLCBsYW1iZGEgPSBjdiRsYW1iZGEubWluKQ0KICAgIH0gZWxzZXsNCiAgICAgIG1vZCA8LSBnbG1uZXQobWF0cml4X3RyYWluX3gxLCB0cmFpbl95MSwgYWxwaGEgPSAwLCBsYW1iZGEgPSBsYW1iZGEwKQ0KICAgIH0NCiAgICByZXMgPC0gcHJlZGljdC5nbG1uZXQobW9kLCBuZXd4ID0geCkNCiAgICByZXR1cm4obGlzdChwcmVkID0gcmVzLA0KICAgICAgICAgICAgICAgIG1vZGVsID0gbW9kKSkNCiAgfQ0KICB0cmVlX21hY2hpbmUgPC0gZnVuY3Rpb24oeCwgcGEgPSBOVUxMKSB7DQogICAgbW9kIDwtIHRyZWUoYXMuZm9ybXVsYShwYXN0ZSgidHJhaW5feTF+IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShpbnB1dF9uYW1lcywgc2VwID0gIiIsIGNvbGxhcHNlID0gIisiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICIiKSksIA0KICAgICAgICAgICAgICAgIGRhdGEgPSBkZl90cmFpbl94MSkNCiAgICByZXMgPC0gYXMudmVjdG9yKHByZWRpY3QobW9kLCB4KSkNCiAgICByZXR1cm4obGlzdChwcmVkID0gcmVzLA0KICAgICAgICAgICAgICAgIG1vZGVsID0gbW9kKSkNCiAgfQ0KICBrbm5fbWFjaGluZSA8LSBmdW5jdGlvbih4LCBrMCkgew0KICAgIG1vZCA8LSBrbm4ucmVnKHRyYWluID0gbWF0cml4X3RyYWluX3gxLCB0ZXN0ID0geCwgeSA9IHRyYWluX3kxLCBrID0gazApDQogICAgcmVzID0gbW9kJHByZWQNCiAgICByZXR1cm4obGlzdChwcmVkID0gcmVzLA0KICAgICAgICAgICAgICAgIG1vZGVsID0gazApKQ0KICB9DQogIFJGX21hY2hpbmUgPC0gZnVuY3Rpb24oeCwgbnRyZWUwKSB7DQogICAgaWYoaXMubnVsbChtdHJ5KSl7DQogICAgICBtb2QgPC0gcmFuZG9tRm9yZXN0KHggPSBkZl90cmFpbl94MSwgeSA9IHRyYWluX3kxLCBudHJlZSA9IG50cmVlMCkNCiAgICB9ZWxzZXsNCiAgICAgIG1vZCA8LSByYW5kb21Gb3Jlc3QoeCA9IGRmX3RyYWluX3gxLCB5ID0gdHJhaW5feTEsIG50cmVlID0gbnRyZWUwLCBtdHJ5ID0gbXRyeSkNCiAgICB9DQogICAgcmVzIDwtIGFzLnZlY3RvcihwcmVkaWN0KG1vZCwgeCkpDQogICAgcmV0dXJuKGxpc3QocHJlZCA9IHJlcywNCiAgICAgICAgICAgICAgICBtb2RlbCA9IG1vZCkpDQogIH0NCiAgeGdiX21hY2hpbmUgPSBmdW5jdGlvbih4LCBucm91bmRzX3hnYjApew0KICAgIG1vZCA8LSB4Z2Jvb3N0KGRhdGEgPSBtYXRyaXhfdHJhaW5feDEsIA0KICAgICAgICAgICAgICAgICAgIGxhYmVsID0gdHJhaW5feTEsIA0KICAgICAgICAgICAgICAgICAgIGV0YSA9IGV0YV94Z2IsDQogICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IG5yb3VuZHNfeGdiMCwNCiAgICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsDQogICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gZWFybHlfc3RvcF94Z2IsDQogICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gbWF4X2RlcHRoX3hnYiwNCiAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gMCkNCiAgICByZXMgPC0gcHJlZGljdChtb2QsIHgpDQogICAgcmV0dXJuKGxpc3QocHJlZCA9IHJlcywNCiAgICAgICAgICAgICAgICBtb2RlbCA9IG1vZCkpDQogIH0NCiAgDQogICMgQWxsIG1hY2hpbmVzDQogIGFsbF9tYWNoaW5lcyA8LSBsaXN0KGxhc3NvID0gbGFzc29fbWFjaGluZSwgDQogICAgICAgICAgICAgICAgICAgICAgIHJpZGdlID0gcmlkZ2VfbWFjaGluZSwgDQogICAgICAgICAgICAgICAgICAgICAgIGtubiA9IGtubl9tYWNoaW5lLCANCiAgICAgICAgICAgICAgICAgICAgICAgdHJlZSA9IHRyZWVfbWFjaGluZSwgDQogICAgICAgICAgICAgICAgICAgICAgIHJmID0gUkZfbWFjaGluZSwNCiAgICAgICAgICAgICAgICAgICAgICAgeGdiID0geGdiX21hY2hpbmUpDQogICMgQWxsIHBhcmFtZXRlcnMNCiAgYWxsX3BhcmFtZXRlcnMgPC0gbGlzdChsYXNzbyA9IGxhbWJkYSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgcmlkZ2UgPSBsYW1iZGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGtubiA9IGssIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRyZWUgPSAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICByZiA9IG50cmVlLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHhnYiA9IG5yb3VuZHNfeGdiKQ0KICBpZihpcy5udWxsKG1hY2hpbmVzKSl7DQogICAgbWFjaCA8LSBjKCJsYXNzbyIsICJyaWRnZSIsICJrbm4iLCAidHJlZSIsICJyZiIsICJ4Z2IiKQ0KICB9ZWxzZXsNCiAgICBtYWNoIDwtIG1hY2hpbmVzDQogIH0NCiAgIyBFeHRyYWN0aW5nIGRhdGENCiAgTSA8LSBsZW5ndGgobWFjaCkNCiAgc2l6ZV9EMSA8LSBmbG9vcihzcGxpdHMqaW5wdXRfc2l6ZVsxXSkNCiAgaWRfRDEgPC0gbG9naWNhbChpbnB1dF9zaXplWzFdKQ0KICBpZF9EMVtzYW1wbGUoaW5wdXRfc2l6ZVsxXSwgc2l6ZV9EMSldIDwtIFRSVUUNCiAgDQogIGRmX3RyYWluX3gxIDwtIGRmX2lucHV0W2lkX0QxLF0NCiAgbWF0cml4X3RyYWluX3gxIDwtIG1hdHJpeF9pbnB1dFtpZF9EMSxdDQogIHRyYWluX3kxIDwtIHRyYWluX3Jlc3BvbnNlW2lkX0QxXQ0KICBkZl90cmFpbl94MiA8LSBkZl9pbnB1dFshaWRfRDEsXQ0KICBtYXRyaXhfdHJhaW5feDIgPC0gbWF0cml4X2lucHV0WyFpZF9EMSxdDQogIA0KICAjIEZ1bmN0aW9uIHRvIGV4dHJhY3QgZGYgYW5kIG1vZGVsIGZyb20gJ21hcCcgZnVuY3Rpb24NCiAgZXh0cl9kZiA8LSBmdW5jdGlvbih4LCBpZCl7DQogICAgcmV0dXJuKHRpYmJsZSgicl97e2lkfX0iOj0gYXMudmVjdG9yKHByZWRfbVtbeF1dJHByZWQpKSkNCiAgfQ0KICBleHRyX21vZCA8LSBmdW5jdGlvbih4LCBpZCl7DQogICAgcmV0dXJuKHByZWRfbVtbeF1dJG1vZGVsKQ0KICB9DQogIA0KICBwcmVkX0QyIDwtIGMoKQ0KICBhbGxfbW9kIDwtIGMoKQ0KICBjYXQoIlxuKiBCdWlsZGluZyBiYXNpYyBtYWNoaW5lcyAuLi5cbiIpDQogIGNhdCgiXHR+IFByb2dyZXNzOiIpDQogIGZvcihtIGluIDE6TSl7DQogICAgaWYobWFjaFttXSAlaW4lIGMoInRyZWUiLCAicmYiKSl7DQogICAgICB4MF90ZXN0IDwtIGRmX3RyYWluX3gyDQogICAgfSBlbHNlIHsNCiAgICAgIHgwX3Rlc3QgPC0gbWF0cml4X3RyYWluX3gyDQogICAgfQ0KICAgIGlmKGlzLm51bGwoYWxsX3BhcmFtZXRlcnNbW21hY2hbbV1dXSkpew0KICAgICAgcGFyYV8gPC0gMQ0KICAgIH1lbHNlew0KICAgICAgcGFyYV8gPC0gYWxsX3BhcmFtZXRlcnNbW21hY2hbbV1dXQ0KICAgIH0NCiAgICBwcmVkX20gPC0gIG1hcChwYXJhXywgDQogICAgICAgICAgICAgICAgICAgLmYgPSB+IGFsbF9tYWNoaW5lc1tbbWFjaFttXV1dKHgwX3Rlc3QsIC54KSkNCiAgICB0ZW0wIDwtIGltYXBfZGZjKC54ID0gMTpsZW5ndGgocGFyYV8pLCANCiAgICAgICAgICAgICAgICAgICAgIC5mID0gZXh0cl9kZikNCiAgICB0ZW0xIDwtIGltYXAoLnggPSAxOmxlbmd0aChwYXJhXyksIA0KICAgICAgICAgICAgICAgICAuZiA9IGV4dHJfbW9kKQ0KICAgIG5hbWVzKHRlbTApIDwtIG5hbWVzKHRlbTEpIDwtIHBhc3RlMChtYWNoW21dLCAxOmxlbmd0aChwYXJhXykpDQogICAgcHJlZF9EMiA8LSBiaW5kX2NvbHMocHJlZF9EMiwgYXNfdGliYmxlKHRlbTApKQ0KICAgIGFsbF9tb2RbW21hY2hbbV1dXSA8LSB0ZW0xDQogICAgY2F0KCIgLi4uICIsIHJvdW5kKG0vTSwgMikqMTAwTCwiJSIsIHNlcCA9ICIiKQ0KICB9DQogIG1heF9NIDwtIG1pbl9NIDwtIE5VTEwNCiAgcHJlZF9EMl8gPC0gcHJlZF9EMg0KICBpZihzY2FsZV9tYWNoaW5lKXsNCiAgICBtYXhfTSA8LSBtYXBfZGJsKC54ID0gcHJlZF9EMiwgLmYgPSBtYXgpDQogICAgbWluX00gPC0gbWFwX2RibCgueCA9IHByZWRfRDIsIC5mID0gbWluKQ0KICAgIHByZWRfRDIgPC0gc2NhbGUocHJlZF9EMiwgY2VudGVyID0gbWluX00sIHNjYWxlID0gbWF4X00gLSBtaW5fTSkNCiAgfQ0KICByZXR1cm4obGlzdChmaXR0ZWRfcmVtYWluID0gcHJlZF9EMiwNCiAgICAgICAgICAgICAgbW9kZWxzID0gYWxsX21vZCwNCiAgICAgICAgICAgICAgaWQyID0gIWlkX0QxLA0KICAgICAgICAgICAgICB0cmFpbl9kYXRhID0gbGlzdCh0cmFpbl9pbnB1dCA9IHRyYWluX2lucHV0X3NjYWxlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdF9yZW1haW5fb3JnID0gcHJlZF9EMl8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9tYWNoaW5lID0gbWluX00sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9tYWNoaW5lID0gbWF4X00sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9pbnB1dCA9IG1pbnMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pbnB1dCA9IG1heHMpKSkNCn0NCmBgYA0KDQotLS0NCg0KPiAqKkV4YW1wbGUuMSoqOiBJbiB0aGlzIGV4YW1wbGUsIHRoZSBtZXRob2QgaXMgaW1wbGVtZW50ZWQgb24gYEJvc3RvbmAgZGF0YSBvZiBgTUFTU2AgbGlicmFyeS4gVGhlIGJhc2ljIG1hY2hpbmVzICJyZiIsICJrbm4iIGFuZCAieGdiIiBhcmUgYnVpbHQgb24gdGhlIGZpcnN0IHBhcnQgb2YgdGhlIHRyYWluaW5nIGRhdGEgKCRcbWF0aGNhbHtEfV97a30kKSwgYW5kIHRoZSAqUm9vdCBNZWFuIFNxdWFyZSBFcnJvcnMqIChSTVNFKSBldmFsdWF0ZWQgb24gdGhlIHNlY29uZCBwYXJ0IG9mIHRoZSB0cmFpbmluZyBkYXRhICgkXG1hdGhjYWx7RH1fe1xlbGx9JCkgdXNlZCBmb3IgYWdncmVnYXRpb24pIGFyZSByZXBvcnRlZC4NCg0KLS0tDQoNCmBgYHtyfQ0KcGFjbWFuOjpwX2xvYWQoTUFTUykNCmRmIDwtIEJvc3Rvbg0KYmFzaWNfbWFjaGluZXMgPC0gZ2VuZXJhdGVNYWNoaW5lc19NaXgodHJhaW5faW5wdXQgPSBkZlssMToxM10sDQogICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0gZGZbLDE0XSwNCiAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IGMoInJmIiwgImtubiIsICJ4Z2IiKSwNCiAgICAgICAgICAgICAgICAgYmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY1BhcmFtZXRlcl9NaXgobGFtYmRhID0gMToxMC8xMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDEwOjIwICogMjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gYygyOjEwKSkpDQpiYXNpY19tYWNoaW5lcyR0cmFpbl9kYXRhJHByZWRpY3RfcmVtYWluX29yZyAlPiUNCiAgc3dlZXAoMSwgZGZbYmFzaWNfbWFjaGluZXMkaWQyLCAibWVkdiJdKSAlPiUNCiAgLl4yICU+JQ0KICBjb2xNZWFucyAlPiUNCiAgdCAlPiUNCiAgc3FydCAlPiUNCiAgYXNfdGliYmxlDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij48dT5PcHRpbWl6YXRpb24gYWxnb3JpdGhtPC91Pjwvc3Bhbj4NCj09PQ0KDQpUaGlzIHBhcnQgcHJvdmlkZXMgZnVuY3Rpb25zIHRvIGFwcHJveGltYXRlIHRoZSBrZXkgcGFyYW1ldGVycyAkKFxhbHBoYSxcYmV0YSlcaW4oXG1hdGhiYntSfV8rXiopXjIkIG9mIHRoZSBhZ2dyZWdhdGlvbi4gVHdvIGltcG9ydGFudCBvcHRpbWl6YXRpb24gbWV0aG9kcyBhcmUgaW1wbGVtZW50ZWQ6ICoqZ3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG0qKiAoYGdyYWRgKSBhbmQgKipncmlkIHNlYXJjaCoqIChgZ3JpZGApLiANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+R3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG08L3U+PC9zcGFuPg0KLS0tDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij5GdW5jdGlvbjwvc3Bhbj4gOiBgc2V0R3JhZFBhcmFtZXRlcl9NaXhgDQoNClRoaXMgZnVuY3Rpb24gYWxsb3dzIHVzIHRvIHNldCB0aGUgdmFsdWVzIG9mIHBhcmFtZXRlcnMgbmVlZGVkIHRvIHByb2Nlc3MgdGhlIGdyYWRpZW50IGRlc2NlbnQgYWxnb3JpdGhtIHRvIGFwcHJveGltYXRlIHRoZSBoeXBlcnBhcmFtZXRlciBvZiB0aGUgYWdncmVnYXRpb24gbWV0aG9kLiANCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGB2YWxfaW5pdGAgOiBhIDJEIHZlY3RvciBvZiBpbml0aWFsIHZhbHVlIG9mIGdyYWRpZW50IGRlc2NlbnQgaXRlcmF0aW9uLiBCeSBkZWZhdWx0LCBgdmFsX2luaXQgPSBOVUxMYCBhbmQgdGhlIGFsZ29yaXRobSB3aWxsIHNlbGVjdCB0aGUgYmVzdCB2YWx1ZSAod2l0aCBzbWFsbGVzdCBjb3N0IGZ1bmN0aW9uKSBhbW9uZyBgYWxwaGFfcmFuZ2VgIGFuZCBgYmV0YV9yYW5nZWAgdmFsdWVzIG9mIHBhcmFtZXRlcnMuDQogICAgLSBgcmF0ZWAgOiB0aGUgMkQgcmVhbCB2YWx1ZWQgdmVjdG9yIG9yIGEgc3RyaW5nIG9mIGxlYXJuaW5nIHJhdGUgaW4gZ3JhZGVudCBkZXNjZW50IGFsZ29yaXRobS4gQnkgZGVmYXVsdCwgYHJhdGUgPSBOVUxMYCAob3IgImF1dG8iKSBhbmQgdGhlIHZhbHVlIGBjb2VmX2F1dG8gPSBjKDAuMSwgMC4xKWAgd2lsbCBiZSB1c2VkLiBJdCBjYW4gYWxzbyBiZSBhIGZ1bmN0aW9uYWwgcmF0ZSwgd2hpY2ggaXMgYSBzdHJpbmcgZWxlbWVudCBvZiB7ImxvZ2FyaXRobSIsICJzcXJ0cm9vdCIsICJsaW5lYXIiLCAicG9seW5vbWlhbCIsICJleHBvbmVudGlhbCJ9LiBFYWNoIHJhdGUgaXMgZGVmaW5lZCBhY2NvcmRpbmcgdG8gYGNvZWZfYCB0eXBlIGFyZ3VtZW50cyBiZWxsb3cuDQogICAgLSBgYWxwaGFfcmFuZ2VgIDogYSByYW5nZSB2ZWN0b3Igb2YgJFxhbHBoYSQgdmFsdWVzIHRvIGJlIGNvbnNpZGVyZWQgYXMgdGhlIGluaXRpYWwgdmFsdWUgaW4gZ3JhZGllbnQgc3RlcC4gQnkgZGVmYXVsdCwgYGFscGhhX3JhbmdlID0gc2VxKDAuMDAwMSwgMTAsIGxlbmd0aC5vdXQgPSA1KWAuDQogICAgLSBgYmV0YV9yYW5nZWAgOiBhIHJhbmdlIHZlY3RvciBvZiAkXGJldGEkIHZhbHVlcyB0byBiZSBjb25zaWRlcmVkIGFzIHRoZSBpbml0aWFsIHZhbHVlIGluIGdyYWRpZW50IHN0ZXAuIEJ5IGRlZmF1bHQsIGBiZXRhX3JhbmdlID0gc2VxKDAuMSwgNTAsIGxlbmd0aC5vdXQgPSA1KWAuDQogICAgLSBgbWF4X2l0ZXJgIDogbWF4aW11bSBpdGVydGFpb24gb2YgZ3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG0uIEJ5IGRlZmF1bHQsIGBtYXhfaXRlciA9IDMwMGAuDQogICAgLSBgcHJpbnRfc3RlcGAgOiBhIGxvZ2ljYWwgdmFsdWUgY29udHJvbGxpbmcgd2hldGhlciB0byBwcmludCB0aGUgcmVzdWx0IG9mIGVhY2ggZ3JhZGllbnQgc3RlcCBvciBub3QgaW4gb3JkZXIgdG8ga2VlcCB0cmFjayBvZiB0aGUgYWxnb3JpdGhtLiBCeSBkZWZhdWx0LCBgcHJpbnRfc3RlcCA9IFRSVUVgLg0KICAgIC0gYHByaW50X3Jlc3VsdGAgOiBhIGxvZ2ljYWwgdmFsdWUgY29udHJvbGxpbmcgd2hldGhlciB0byBwcmludCB0aGUgcmVzdWx0IG9mIHRoZSBhbGdvcml0aG0gb3Igbm90LiBCeSBkZWZhdWx0LCBgcHJpbnRfcmVzdWx0ID0gVFJVRWAuDQogICAgLSBgZmlndXJlYCA6IGEgbG9naWNhbCB2YWx1ZSBjb250cm9sbGluZyB3aGV0aGVyIHRvIHBsb3QgYSBncmFwaGljIG9mIHRoZSByZXN1bHQgb3Igbm90LiBCeSBkZWZhdWx0LCBgZmlndXJlID0gVFJVRWAuDQogICAgLSBgY29lZl9hdXRvYCA6IHRoZSBjb25zdGFudCBsZWFybmluZyByYXRlIHdoZW4gYHJhdGUgPSBOVUxMYC4gQnkgZGVmYXVsdCwgYGNvZWZfYXV0byA9IGMoMSwgMSlgLg0KICAgIC0gYGNvZWZfbG9nYCA6IHRoZSBjb2VmZmljaW5ldCBtdWx0aXBseWluZyB0byB0aGUgKmxvZ2FyaXRobWljKiBpbmNyZW1lbnQgb2YgdGhlIGxlYXJuaW5nIHJhdGUsIGkuZS4sIHRoZSByYXRlIGlzIGByYXRlYCAkPSQgYGNvZWZfbG9nYCRcdGltZXMgXGxvZygxK3QpJCB3aGVyZSAkdCQgaXMgdGhlIG51bWVyIG51bWJlciBvZiBpdGVyYXRpb24uIEJ5IGRlZmF1bHQsIGBjb2VmX2xvZyA9IDFgLg0KICAgIC0gYGNvZWZfc3FydGAgOiB0aGUgY29lZmZpY2luZXQgbXVsdGlwbHlpbmcgdG8gdGhlICpzcXVhcmUgcm9vdCogaW5jcmVtZW50IG9mIHRoZSBsZWFybmluZyByYXRlLCBpLmUuLCB0aGUgcmF0ZSBpcyBgcmF0ZWAgJD0kIGBjb2VmX3NxcnRgJFx0aW1lcyBcc3FydHt0fSQuIEJ5IGRlZmF1bHQsIGBjb2VmX3NxcnQgPSAxYC4NCiAgICAtIGBjb2VmX2xtYCA6IHRoZSBjb2VmZmljaW5ldCBtdWx0aXBseWluZyB0byB0aGUgKmxpbmVhciogaW5jcmVtZW50IG9mIHRoZSBsZWFybmluZyByYXRlLCBpLmUuLCB0aGUgcmF0ZSBpcyBgcmF0ZWAgJD0kIGBjb2VmX2xtYCRcdGltZXMgdCQuIEJ5IGRlZmF1bHQsIGBjb2VmX2xtID0gMWAuDQogICAgLSBgZGVnX3BvbHlgIDogdGhlIGRlZ3JlZSBvZiB0aGUgKnBvbHlub21pYWwqIGluY3JlbWVudCBvZiB0aGUgbGVhcm5pbmcgcmF0ZSwgaS5lLiwgdGhlIHJhdGUgaXMgYHJhdGVgICQ9dF57XHRleHR0dHtjb2VmX3BvbHl9fSQuIEJ5IGRlZmF1bHQsIGBkZWdfcG9seSA9IDJgLg0KICAgIC0gYGJhc2VfZXhwYCA6IHRoZSBiYXNlIG9mIHRoZSAqZXhwb25lbnRpYWwqIGluY3JlbWVudCBvZiB0aGUgbGVhcm5pbmcgcmF0ZSwgaS5lLiwgdGhlIHJhdGUgaXMgYHJhdGVgICQ9JCBgYmFzZV9leHBgJF50JC4gQnkgZGVmYXVsdCwgYGJhc2VfZXhwID0gMS41YC4NCiAgICAtIGBheGVzYCA6IG5hbWVzIG9mICR4LHkkIGFuZCAkeiQtYXhpcyByZXNwZWN0aXZlbHkuIEJ5IGRlZmF1bHQsIGBheGVzID0gYygiYWxwaGEiLCAiYmV0YSIsICJMMSBub3JtIG9mIGdyYWRpZW50IilgLg0KICAgIC0gYHRpdGxlYCA6IHRoZSB0aXRsZSBvZiB0aGUgcGxvdC4gQnkgZGVmYXVsdCwgYHRpdGxlID0gTlVMTGAgYW5kIHRoZSBkZWZhdWx0IHRpdGxlIGlzIGBHcmFkaWVudCBzdGVwYC4NCiAgICAtIGB0aHJlc2hvbGRgIDogdGhlIHRocmVzaG9sZCB0byBzdG9wIHRoZSBhbGdvcml0aG0gd2hhdCB0aGUgcmVsYXRpdmUgY2hhbmdlIGlzIHNtYWxsZXIgdGhhbiB0aGlzIHZhbHVlLiBCeSBkZWZhdWx0LCBgdGhyZXNob2xkID0gMWUtMTBgLg0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSAqbGlzdCogb2YgYWxsIHRoZSBwYXJhbWV0ZXJzIGdpdmVuIGluIGl0cyBhcmd1bWVudHMuDQoNCmBgYHtyfQ0Kc2V0R3JhZFBhcmFtZXRlcl9NaXggPC0gZnVuY3Rpb24odmFsX2luaXQgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYXRlID0gTlVMTCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhX3JhbmdlID0gc2VxKDAuMDAwMSwgMTAsIGxlbmd0aC5vdXQgPSA1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmV0YV9yYW5nZSA9IHNlcSgwLjEsIDUwLCBsZW5ndGgub3V0ID0gNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pdGVyID0gMzAwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfc3RlcCA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9yZXN1bHQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZl9hdXRvID0gYygwLjEsMC4xKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZl9sb2cgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2VmX3NxcnQgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2VmX2xtID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVnX3BvbHkgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXNlX2V4cCA9IDEuNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhlcyA9IGMoImFscGhhIiwgImJldGEiLCAiTDEgbm9ybSBvZiBncmFkaWVudCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IDFlLTEwKSB7DQogIHJldHVybigNCiAgICBsaXN0KHZhbF9pbml0ID0gdmFsX2luaXQsDQogICAgICByYXRlID0gcmF0ZSwNCiAgICAgIGFscGhhX3JhbmdlID0gYWxwaGFfcmFuZ2UsDQogICAgICBiZXRhX3JhbmdlID0gYmV0YV9yYW5nZSwNCiAgICAgIG1heF9pdGVyID0gbWF4X2l0ZXIsDQogICAgICBwcmludF9zdGVwID0gcHJpbnRfc3RlcCwNCiAgICAgIHByaW50X3Jlc3VsdCA9IHByaW50X3Jlc3VsdCwNCiAgICAgIGZpZ3VyZSA9IGZpZ3VyZSwNCiAgICAgIGNvZWZfYXV0byA9IGNvZWZfYXV0bywNCiAgICAgIGNvZWZfbG9nID0gY29lZl9sb2csDQogICAgICBjb2VmX3NxcnQgPSBjb2VmX3NxcnQsDQogICAgICBjb2VmX2xtID0gY29lZl9sbSwNCiAgICAgIGRlZ19wb2x5ID0gZGVnX3BvbHksDQogICAgICBiYXNlX2V4cCA9IGJhc2VfZXhwLA0KICAgICAgYXhlcyA9IGF4ZXMsDQogICAgICB0aXRsZSA9IHRpdGxlLA0KICAgICAgdGhyZXNob2xkID0gdGhyZXNob2xkDQogICAgKQ0KICApDQp9DQpgYGANCg0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+RnVuY3Rpb248L3NwYW4+IDogYGdyYWRPcHRpbWl6ZXJfTWl4YA0KDQpUaGlzIGZ1bmN0aW9uIHBlcmZvcm1zIGdyYWRpZW50IGRlc2NlbnQgYWxnb3JpdGhtIHRvIGFwcHJveGltYXRlIHRoZSBtaW5pbWl6ZXIgb2YgYW55IGdpdmVuIGZ1bmN0aW9ucyAoY29udmV4IG9yIGxvY2FsbHkgY29udmV4IGFyb3VuZCBpdHMgb3B0aW1pemVyKS4NCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBvYmpfZnVuYCA6IHRoZSBvYmplY3RpdmUgZnVuY3Rpb24gZm9yIHdoaWNoIGl0cyBtaW5pbWl6ZXIgaXMgdG8gYmUgZXN0aW1hdGVkLiBJdCBzaG91bGQgdGFrZSBhIDJEIHZlY3RvciBhcyBhbiBpbnB1dC4NCiAgICAtIGBzZXRQYXJhbWV0ZXJgIDogdGhlIGNvbnRyb2wgb2YgZ3JhZGllbnQgZGVzY2VudCBwYXJhbWV0ZXJzIHdoaWNoIHNob3VsZCBiZSB0aGUgZnVuY3Rpb24gYHNldEdyYWRQYXJhbWV0ZXJfTWl4KClgIGRlZmluZWQgZWFybGllci4NCg0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCg0KICAgIC0gYG9wdF9wYXJhbWAgOiB0aGUgb2JzZXJ2ZWQgdmFsdWUgb2YgdGhlIG1pbmltaXplci4NCiAgICAtIGBvcHRfZXJyb3JgIDogdGhlIHZhbHVlIG9mIHRoZSBvcHRpbWFsIHJpc2suDQogICAgLSBgYWxsX2dyYWRgIDogdGhlIHZlY3RvciBvZiBhbGwgdGhlIGdyYWRpZW50cyBjb2xsZWN0ZWQgZHVyaW5nIHRoZSB3YWxrIG9mIHRoZSBhbGdvcml0aG0uDQogICAgLSBgYWxsX3BhcmFtYCA6IHRoZSB2ZWN0b3Igb2YgYWxsIHBhcmFtZXRlcnMgY29sbGVjdGVkIGR1cmluZyB0aGUgd2FsayBvZiB0aGUgYWxnb3JpdGhtLg0KICAgIC0gYHJ1bl90aW1lYCA6IHRoZSBydW5uaW5nIHRpbWUgb2YgdGhlIGFsZ29yaXRobS4gDQoNCmBgYHtyIH0NCmdyYWRPcHRpbWl6ZXJfTWl4IDwtIGZ1bmN0aW9uKG9ial9mdW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KCkpIHsNCiAgc3RhcnQudGltZSA8LSBTeXMudGltZSgpDQogICMgT3B0aW1pemF0aW9uIHN0ZXA6DQogICMgPT09PT09PT09PT09PT09PT09DQogIHNwZWNfcHJpbnQgPC0gZnVuY3Rpb24oeCwgZGlnID0gNSkgcmV0dXJuKGlmZWxzZSh4ID4gMWUtNiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQoeCwgZGlnaXQgPSBkaWcsIG5zbWFsbCA9IGRpZyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybWF0KHgsIHNjaWVudGlmaWMgPSBUUlVFLCBkaWdpdCA9IGRpZywgbnNtYWxsID0gZGlnKSkpDQogIGNvbGxlY3RfdmFsIDwtIGMoKQ0KICBncmFkaWVudHMgPC0gYygpDQogIGlmIChpcy5udWxsKHNldFBhcmFtZXRlciR2YWxfaW5pdCkpew0KICAgIHJhbmdlX2FscCA8LSByZXAoc2V0UGFyYW1ldGVyJGFscGhhX3JhbmdlLCBsZW5ndGgoc2V0UGFyYW1ldGVyJGJldGFfcmFuZ2UpKQ0KICAgIHJhbmdlX2JldCA8LSByZXAoc2V0UGFyYW1ldGVyJGJldGFfcmFuZ2UsIGxlbmd0aChzZXRQYXJhbWV0ZXIkYWxwaGFfcmFuZ2UpKQ0KICAgIHRlbSA8LSBtYXAyX2RibCgueCA9IHJhbmdlX2FscCwNCiAgICAgICAgICAgICAgICAgICAgLnkgPSByYW5nZV9iZXQsDQogICAgICAgICAgICAgICAgICAgIC5mID0gfiBvYmpfZnVuKGMoLngsIC55KSkpDQogICAgaWQwIDwtIHdoaWNoLm1pbih0ZW0pDQogICAgdmFsIDwtIHZhbDAgPC0gYyhyYW5nZV9hbHBbaWQwXSwgcmFuZ2VfYmV0W2lkMF0pDQogICAgZ3JhZF8gPC0gcHJhY21hOjpncmFkKA0KICAgICAgZiA9IG9ial9mdW4sDQogICAgICB4MCA9IHZhbDAsDQogICAgICBoZXBzID0gLk1hY2hpbmUkZG91YmxlLmVwcyBeICgxIC8gMykpDQogIH0gZWxzZXsNCiAgICB2YWwgPC0gdmFsMCA8LSBzZXRQYXJhbWV0ZXIkdmFsX2luaXQNCiAgICBncmFkXyA8LSBwcmFjbWE6OmdyYWQoDQogICAgICBmID0gb2JqX2Z1biwgDQogICAgICB4MCA9IHZhbDAsIA0KICAgICAgaGVwcyA9IC5NYWNoaW5lJGRvdWJsZS5lcHMgXiAoMSAvIDMpKQ0KICB9DQogIGlmKHNldFBhcmFtZXRlciRwcmludF9zdGVwKXsNCiAgICBjYXQoIlxuKiBHcmFkaWVudCBkZXNjZW50IGFsZ29yaXRobSAuLi4iKQ0KICAgIGNhdCgiXG4gIFN0ZXBcdHwgIGFscGhhICAgIDsgIGJldGEgICBcdHwgIEdyYWRpZW50IChhbHBoYSA7IGJldGEpXHR8ICBUaHJlc2hvbGQgXG4iKQ0KICAgIGNhdCgiICIsIHJlcCgiLSIsIDgwKSwgc2VwID0gIiIpDQogICAgY2F0KCJcbiAgIDAgXHR8ICIsIHNwZWNfcHJpbnQodmFsMFsxXSksIiA7ICIsIHNwZWNfcHJpbnQodmFsMFsyXSksDQogICAgICAgICJcdHwgIiwgc3BlY19wcmludChncmFkX1sxXSwgNiksICIgOyAiLCBzcGVjX3ByaW50KGdyYWRfWzJdLCA1KSwgDQogICAgICAgICIgXHR8ICIsIHNldFBhcmFtZXRlciR0aHJlc2hvbGQsICJcbiIpDQogICAgY2F0KCIgIiwgcmVwKCItIiw4MCksIHNlcCA9ICIiKQ0KICB9DQogIGlmIChpcy5udW1lcmljKHNldFBhcmFtZXRlciRyYXRlKSl7DQogICAgbGFtYmRhMCA8LSBzZXRQYXJhbWV0ZXIkcmF0ZSAvIGFicyhncmFkXykNCiAgICByYXRlX0dEIDwtICJhdXRvIg0KICB9IGVsc2V7DQogICAgcjAgPC0gc2V0UGFyYW1ldGVyJGNvZWZfYXV0byAvIGFicyhncmFkXykNCiAgICAjIFJhdGUgZnVuY3Rpb25zDQogICAgcmF0ZV9mdW5jIDwtIGxpc3QoYXV0byA9IHIwLCANCiAgICAgICAgICAgICAgICAgICAgICBsb2dhcml0aG0gPSBmdW5jdGlvbihpKSAgc2V0UGFyYW1ldGVyJGNvZWZfbG9nICogbG9nKDIgKyBpKSAqIHIwLA0KICAgICAgICAgICAgICAgICAgICAgIHNxcnRyb290ID0gZnVuY3Rpb24oaSkgc2V0UGFyYW1ldGVyJGNvZWZfc3FydCAqIHNxcnQoaSkgKiByMCwNCiAgICAgICAgICAgICAgICAgICAgICBsaW5lYXIgPSBmdW5jdGlvbihpKSBzZXRQYXJhbWV0ZXIkY29lZl9sbSAqIChpKSAqIHIwLA0KICAgICAgICAgICAgICAgICAgICAgIHBvbHlub21pYWwgPSBmdW5jdGlvbihpKSBpIF4gc2V0UGFyYW1ldGVyJGRlZ19wb2x5ICogcjAsDQogICAgICAgICAgICAgICAgICAgICAgZXhwb25lbnRpYWwgPSBmdW5jdGlvbihpKSBzZXRQYXJhbWV0ZXIkYmFzZV9leHAgXiBpICogcjApDQogICAgcmF0ZV9HRCA8LSBtYXRjaC5hcmcoc2V0UGFyYW1ldGVyJHJhdGUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGMoImF1dG8iLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgImxvZ2FyaXRobSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAic3FydHJvb3QiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgImxpbmVhciIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAicG9seW5vbWlhbCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAiZXhwb25lbnRpYWwiKSkNCiAgICBsYW1iZGEwIDwtIHJhdGVfZnVuY1tbcmF0ZV9HRF1dDQogIH0NCiAgaSA8LSAwDQogIGdyYWQwIDwtIDEwKmdyYWRfIA0KICBpZiAoaXMubnVtZXJpYyhzZXRQYXJhbWV0ZXIkcmF0ZSkgfCByYXRlX0dEID09ICJhdXRvIikgew0KICAgIHdoaWxlIChpIDwgc2V0UGFyYW1ldGVyJG1heF9pdGVyKSB7DQogICAgICBpZihhbnkoaXMubmEoZ3JhZF8pKSl7DQogICAgICAgIHZhbDAgPC0gYyhydW5pZigxLCB2YWwwWzFdKjAuOTksIHZhbDBbMV0qMS4wMSksIA0KICAgICAgICAgICAgICAgICAgcnVuaWYoMSwgdmFsMFsyXSowLjk5LCB2YWwwWzJdKjEuMDEpKSANCiAgICAgICAgZ3JhZF8gPSBwcmFjbWE6OmdyYWQoDQogICAgICAgICAgZiA9IG9ial9mdW4sIA0KICAgICAgICAgIHgwID0gdmFsMCwgDQogICAgICAgICAgaGVwcyA9IC5NYWNoaW5lJGRvdWJsZS5lcHMgXiAoMSAvIDMpDQogICAgICAgKQ0KICAgICAgfQ0KICAgICAgdmFsIDwtIHZhbDAgLSBsYW1iZGEwICogZ3JhZF8NCiAgICAgIGlmIChhbnkodmFsIDwgMCkpew0KICAgICAgICB2YWxbdmFsIDwgMF0gPC0gdmFsMFt2YWwgPCAwXS8yDQogICAgICAgIGxhbWJkYTBbdmFsIDwgMF0gPC0gbGFtYmRhMFt2YWwgPCAwXSAvIDINCiAgICAgIH0NCiAgICAgIGlmKGkgPiA1KXsNCiAgICAgICAgc2lnbl8gPC0gc2lnbihncmFkXykgIT0gc2lnbihncmFkMCkNCiAgICAgICAgaWYoYW55KHNpZ25fKSl7DQogICAgICAgICAgbGFtYmRhMFtzaWduX10gPSBsYW1iZGEwW3NpZ25fXS8yDQogICAgICAgIH0NCiAgICAgIH0NCiAgICAgIHJlbGF0aXZlIDwtIHN1bShhYnModmFsIC0gdmFsMCkpIC8gc3VtKGFicyh2YWwwKSkNCiAgICAgIHRlc3RfdGhyZXNob2xkIDwtIG1heChyZWxhdGl2ZSwgc3VtKGFicyhncmFkXyAtIGdyYWQwKSkpDQogICAgICBpZiAodGVzdF90aHJlc2hvbGQgPiBzZXRQYXJhbWV0ZXIkdGhyZXNob2xkKXsNCiAgICAgICAgdmFsMCA8LSB2YWwNCiAgICAgICAgZ3JhZDAgPC0gZ3JhZF8NCiAgICAgIH0gZWxzZXsNCiAgICAgICAgYnJlYWsNCiAgICAgIH0NCiAgICAgIGdyYWRfIDwtIHByYWNtYTo6Z3JhZCgNCiAgICAgICAgZiA9IG9ial9mdW4sIA0KICAgICAgICB4MCA9IHZhbDAsIA0KICAgICAgICBoZXBzID0gLk1hY2hpbmUkZG91YmxlLmVwcyBeICgxIC8gMykNCiAgICAgICkNCiAgICAgIGkgPC0gaSArIDENCiAgICAgIGlmKHNldFBhcmFtZXRlciRwcmludF9zdGVwKXsNCiAgICAgICAgY2F0KCJcbiAgIiwgaSwgIlx0fCAiLCBzcGVjX3ByaW50KHZhbFsxXSwgNCksICIgOyAiLCBzcGVjX3ByaW50KHZhbFsyXSwgNCksIA0KICAgICAgICAgICAgIlx0fCAiLCBzcGVjX3ByaW50KGdyYWRfWzFdLCA1KSwgIiA7ICIsIHNwZWNfcHJpbnQoZ3JhZF9bMl0sIDUpLCANCiAgICAgICAgICAgICJcdHwgIiwgdGVzdF90aHJlc2hvbGQsICJcciIpDQogICAgICB9DQogICAgICBjb2xsZWN0X3ZhbCA8LSByYmluZChjb2xsZWN0X3ZhbCwgdmFsKQ0KICAgICAgZ3JhZGllbnRzIDwtIHJiaW5kKGdyYWRpZW50cywgZ3JhZF8pDQogICAgfQ0KICB9DQogIGVsc2V7DQogICAgd2hpbGUgKGkgPCBzZXRQYXJhbWV0ZXIkbWF4X2l0ZXIpIHsNCiAgICAgIGlmKGFueShpcy5uYShncmFkXykpKXsNCiAgICAgICAgdmFsMCA8LSBjKHJ1bmlmKDEsIHZhbDBbMV0qMC45OSwgdmFsMFsxXSoxLjAxKSwgDQogICAgICAgICAgICAgICAgICBydW5pZigxLCB2YWwwWzJdKjAuOTksIHZhbDBbMl0qMS4wMSkpIA0KICAgICAgICBncmFkXyA9IHByYWNtYTo6Z3JhZCgNCiAgICAgICAgICBmID0gb2JqX2Z1biwgDQogICAgICAgICAgeDAgPSB2YWwwLCANCiAgICAgICAgICBoZXBzID0gLk1hY2hpbmUkZG91YmxlLmVwcyBeICgxIC8gMykNCiAgICAgICApDQogICAgICB9DQogICAgICB2YWwgPC0gdmFsMCAtIGxhbWJkYTAoaSkgKiBncmFkXw0KICAgICAgaWYgKGFueSh2YWwgPCAwKSl7DQogICAgICAgIHZhbFt2YWwgPCAwXSA8LSB2YWwwW3ZhbCA8IDBdLzINCiAgICAgICAgcjBbdmFsIDwgMF0gPC0gcjBbdmFsIDwgMF0gLyAyDQogICAgICB9DQogICAgICBpZihpID4gNSl7DQogICAgICAgIHNpZ25fIDwtIHNpZ24oZ3JhZF8pICE9IHNpZ24oZ3JhZDApDQogICAgICAgIGlmKGFueShzaWduXykpew0KICAgICAgICAgIHIwW3NpZ25fXSA8LSByMFtzaWduX10gLyAyDQogICAgICAgIH0NCiAgICAgIH0NCiAgICAgIHJlbGF0aXZlIDwtIHN1bShhYnModmFsIC0gdmFsMCkpIC8gc3VtKGFicyh2YWwwKSkNCiAgICAgIHRlc3RfdGhyZXNob2xkIDwtIG1heChyZWxhdGl2ZSwgc3VtKGFicyhncmFkXyAtIGdyYWQwKSkpDQogICAgICBpZiAodGVzdF90aHJlc2hvbGQgPiBzZXRQYXJhbWV0ZXIkdGhyZXNob2xkKXsNCiAgICAgICAgdmFsMCA8LSB2YWwNCiAgICAgICAgZ3JhZDAgPC0gZ3JhZF8NCiAgICAgIH1lbHNlew0KICAgICAgICBicmVhaw0KICAgICAgfQ0KICAgICAgZ3JhZF8gPC0gcHJhY21hOjpncmFkKA0KICAgICAgICBmID0gb2JqX2Z1biwgDQogICAgICAgIHgwID0gdmFsMCwgDQogICAgICAgIGhlcHMgPSAuTWFjaGluZSRkb3VibGUuZXBzIF4gKDEgLyAzKQ0KICAgICAgKQ0KICAgICAgaWYoc2V0UGFyYW1ldGVyJHByaW50X3N0ZXApew0KICAgICAgICBjYXQoIlxuICAiLCBpLCAiXHR8ICIsIHNwZWNfcHJpbnQodmFsWzFdLCA0KSwgIiA7ICIsIHNwZWNfcHJpbnQodmFsWzJdLCA0KSwgDQogICAgICAgICAgICAiXHR8ICIsIHNwZWNfcHJpbnQoZ3JhZF9bMV0sIDUpLCAiIDsgIiwgc3BlY19wcmludChncmFkX1syXSwgNSksIA0KICAgICAgICAgICAgIlx0fCAiLCB0ZXN0X3RocmVzaG9sZCwgIlxyIikNCiAgICAgIH0NCiAgICAgIGkgPC0gaSArIDENCiAgICAgIGNvbGxlY3RfdmFsIDwtIHJiaW5kKGNvbGxlY3RfdmFsLCB2YWwpDQogICAgICBncmFkaWVudHMgPC0gcmJpbmQoZ3JhZGllbnRzLCBncmFkXykNCiAgICB9DQogIH0NCiAgb3B0X2VwIDwtIHZhbA0KICBvcHRfcmlzayA8LSBvYmpfZnVuKG9wdF9lcCkNCiAgaWYoc2V0UGFyYW1ldGVyJHByaW50X3N0ZXApew0KICAgIGNhdChyZXAoIi0iLCA4MCksIHNlcCA9ICIiKQ0KICAgIGlmKHN1bShhYnMoZ3JhZF8pKSA9PSAwKXsNCiAgICAgIGNhdCgiXG4gU3RvcHBlZHwgIiwgc3BlY19wcmludCh2YWxbMV0sIDQpLCAiIDsgIiwgc3BlY19wcmludCh2YWxbMl0sIDQpLCANCiAgICAgICAgIlx0fFx0ICIsIDAsIA0KICAgICAgICAiXHRcdHwgIiwgdGVzdF90aHJlc2hvbGQpDQogICAgfWVsc2V7DQogICAgICBjYXQoIlxuIFN0b3BwZWR8ICIsIHNwZWNfcHJpbnQodmFsWzFdLCA0KSwgIiA7ICIsIHNwZWNfcHJpbnQodmFsWzJdLCA0KSwgDQogICAgICAgICJcdHwgIiwgc3BlY19wcmludChncmFkX1sxXSksICIgOyAiLCBzcGVjX3ByaW50KGdyYWRfWzJdKSwgDQogICAgICAgICJcdHwgIiwgdGVzdF90aHJlc2hvbGQpDQogICAgfSANCiAgfQ0KICBpZihzZXRQYXJhbWV0ZXIkcHJpbnRfcmVzdWx0KXsNCiAgICBjYXQoIlxuIH4gT2JzZXJ2ZWQgcGFyYW1ldGVyOiAoYWxwaGEsIGJldGEpID0gKCIsIG9wdF9lcFsxXSwgIiwgIiwgb3B0X2VwWzJdLCAiKSBpbiIsaSwgIml0ZXJ0YWlvbnMuIikNCiAgfQ0KICBpZiAoc2V0UGFyYW1ldGVyJGZpZ3VyZSkgew0KICAgIGlmKGlzLm51bGwoc2V0UGFyYW1ldGVyJHRpdGxlKSl7DQogICAgICB0aXQgPC0gcGFzdGUoIjxiPiBMMSBub3JtIG9mIGdyYWRpZW50IGFzIGEgZnVuY3Rpb24gb2Y8L2I+ICgiLA0KICAgICAgc2V0UGFyYW1ldGVyJGF4ZXNbMV0sIiwiLCANCiAgICAgIHNldFBhcmFtZXRlciRheGVzWzJdLCANCiAgICAgICIpIikNCiAgICB9IGVsc2V7DQogICAgICB0aXQgPC0gc2V0UGFyYW1ldGVyJHRpdGxlDQogICAgfQ0KICAgIHNpeiA9IGxlbmd0aChjb2xsZWN0X3ZhbFssMV0pDQogICAgZmlnIDwtIHRpYmJsZSh4ID0gY29sbGVjdF92YWxbLDFdLA0KICAgICAgICAgICB5ID0gY29sbGVjdF92YWxbLDJdLA0KICAgICAgICAgICB6ID0gYXBwbHkoYWJzKGdyYWRpZW50cyksIDEsIHN1bSkpICU+JQ0KICAgICAgcGxvdF9seSh4ID0gfngsIHkgPSB+eSkgJT4lIA0KICAgICAgYWRkX3RyYWNlKHogPSB+eiwNCiAgICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsDQogICAgICAgICAgICAgICAgbW9kZSA9ICJsaW5lcyIsDQogICAgICAgICAgICAgICAgbGluZSA9IGxpc3Qod2lkdGggPSA2LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IH56LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcnNjYWxlID0gJ1ZpcmlkaXMnKSwNCiAgICAgICAgICAgICAgICBuYW1lID0gIkdyYWRpZW50IHN0ZXAiKSAlPiUNCiAgICAgIGFkZF90cmFjZSh4ID0gYyhvcHRfZXBbMV0sIG9wdF9lcFsxXSksDQogICAgICAgICAgICAgICAgeSA9IGMoMCwgb3B0X2VwWzJdKSwNCiAgICAgICAgICAgICAgICB6ID0gfmMoeltzaXpdLCB6W3Npel0pLA0KICAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwNCiAgICAgICAgICAgICAgICBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLA0KICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KCANCiAgICAgICAgICAgICAgICAgIHdpZHRoID0gMiwNCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gIiM1RTg4RkMiLCANCiAgICAgICAgICAgICAgICAgIGRhc2ggPSBUUlVFKSwNCiAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAgc2l6ZSA9IDQsDQogICAgICAgICAgICAgICAgICBjb2xvciA9IH5jKCIjNUU4OEZDIiwgIiMzOERFMjUiKSksDQogICAgICAgICAgICAgICAgbmFtZSA9IHBhc3RlKCJPcHRpbWFsIixzZXRQYXJhbWV0ZXIkYXhlc1sxXSkpICU+JQ0KICAgICAgYWRkX3RyYWNlKHggPSBjKDAsIG9wdF9lcFsxXSksDQogICAgICAgICAgICAgICAgeSA9IGMob3B0X2VwWzJdLCBvcHRfZXBbMl0pLA0KICAgICAgICAgICAgICAgIHogPSB+Yyh6W3Npel0sIHpbc2l6XSksDQogICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLA0KICAgICAgICAgICAgICAgIG1vZGUgPSAnbGluZXMrbWFya2VycycsDQogICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoIA0KICAgICAgICAgICAgICAgICAgd2lkdGggPSAyLA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSAiI0YzMTUzNiIsIA0KICAgICAgICAgICAgICAgICAgZGFzaCA9IFRSVUUpLA0KICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoDQogICAgICAgICAgICAgICAgICBzaXplID0gNCwNCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gfmMoIiNGMzE1MzYiLCAiIzM4REUyNSIpKSwNCiAgICAgICAgICAgICAgICBuYW1lID0gcGFzdGUoIk9wdGltYWwiLHNldFBhcmFtZXRlciRheGVzWzJdKSkgICU+JQ0KICAgICAgYWRkX3RyYWNlKHggPSBvcHRfZXBbMV0sDQogICAgICAgICAgICAgICAgeSA9IG9wdF9lcFsyXSwNCiAgICAgICAgICAgICAgICB6ID0gfnpbc2l6XSwNCiAgICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsDQogICAgICAgICAgICAgICAgbW9kZSA9ICdtYXJrZXJzJywNCiAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAgc2l6ZSA9IDUsDQogICAgICAgICAgICAgICAgICBjb2xvciA9ICIjMzhERTI1IiksDQogICAgICAgICAgICAgICAgbmFtZSA9ICJPcHRpbWFsIHBvaW50IikgJT4lDQogICAgICBsYXlvdXQodGl0bGUgPSBsaXN0KHRleHQgPSB0aXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHggPSAwLjA3NSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAwLjkyNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZm9udCA9IGxpc3QoZmFtaWx5ID0gIlZlcmRhbmEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICIjNUU4OEZDIikpLA0KICAgICAgICAgICAgIGxlZ2VuZCA9IGxpc3QoeCA9IDEwMCwgeSA9IDAuNSksDQogICAgICAgICAgICAgc2NlbmUgPSBsaXN0KA0KICAgICAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gc2V0UGFyYW1ldGVyJGF4ZXNbMV0pLA0KICAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gc2V0UGFyYW1ldGVyJGF4ZXNbMl0pLA0KICAgICAgICAgICAgICAgemF4aXMgPSBsaXN0KCB0aXRsZSA9IHNldFBhcmFtZXRlciRheGVzWzNdKSkpDQogICAgZmlnICU+JSBwcmludA0KICB9DQogIGVuZC50aW1lID0gU3lzLnRpbWUoKQ0KICByZXR1cm4obGlzdCgNCiAgICBvcHRfcGFyYW0gPSBvcHRfZXAsDQogICAgb3B0X2Vycm9yID0gb3B0X3Jpc2ssDQogICAgYWxsX2dyYWQgPSBncmFkaWVudHMsDQogICAgYWxsX3BhcmFtID0gY29sbGVjdF92YWwsDQogICAgcnVuX3RpbWUgPSBkaWZmdGltZShlbmQudGltZSwgDQogICAgICAgICAgICAgICAgICAgICAgICBzdGFydC50aW1lLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHVuaXRzID0gInNlY3MiKVtbMV1dDQogICkpDQp9DQpgYGANCg0KLS0tDQoNCj4gKipFeGFtcGxlLjIqKjogQXBwcm94aW1hdGUgJCQoeF4qLHleKik9XHRleHR7YXJnfVxtaW5fe3gseSlcaW5cbWF0aGJie1J9XjJ9Zih4LHkpLCQkIA0Kd2hlcmUgJCRmKHgseSk9KHgtMSleMigxK1xzaW5eMigyLjUoeC0xKSkpKyh5LTEpXjIoMStcc2luXjIoMi41KHktMSkpKSQkDQpOb3RlIHRoYXQgYXJndW1lbnQgYHZhbF9pbml0YCBpcyBjcnVjaWFsIHNpbmNlICRmJCBpcyBub3QgY29udmV4Lg0KDQotLS0NCg0KYGBge3IsIG91dC53aWR0aD0nOTAlJywgZmlnLmFsaWduPSdjZW50ZXInfQ0Kb2JqZWN0X2Z1bmMgPC0gZnVuY3Rpb24oeCkgc3VtKCh4LTEpXjIqKDErc2luKDIuNSooeC0xKSleMikpDQpwIDwtIHRpYmJsZTo6dGliYmxlKHggPSByZXAoc2VxKC00LDYsIGxlbmd0aC5vdXQgPSAzMCksMzApLCANCiAgICAgICAgICAgICAgICAgICAgICB5ID0gcmVwKHNlcSgtMyw3LCBsZW5ndGgub3V0ID0gMzApLCBlYWNoID0gMzApKSAlPiUNCiAgbXV0YXRlKHogPSBtYXAyX2RibCgueCA9IHgsIC55ID0geSwgLmYgPSB+IG9iamVjdF9mdW5jKGMoLngsLnkpKSkpICU+JQ0KICBwbG90X2x5KHggPSB+eCwgeSA9IH55LCB6ID0gfnosIHR5cGUgPSAibWVzaDNkIikgJT4lDQogIGFkZF90cmFjZSh4ID0gMSwNCiAgICAgICAgICAgIHkgPSAxLA0KICAgICAgICAgICAgeiA9IDAsDQogICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGUgPSAibWFya2VycyIsDQogICAgICAgICAgICBuYW1lID0gIk9wdGltYWwgcG9pbnQiKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkNvc3QgZnVuY3Rpb24iKQ0Kc2hvdyhwKQ0KZ2QgPC0gZ3JhZE9wdGltaXplcl9NaXgob2JqX2Z1biA9IG9iamVjdF9mdW5jLA0KICAgICAgICAgICAgICAgICAgc2V0UGFyYW1ldGVyID0gc2V0R3JhZFBhcmFtZXRlcl9NaXgodmFsX2luaXQgPSBjKDIuNCwgMy41KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhdGUgPSAibG9nIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvZWZfYXV0byA9IGMoMC43LCAwLjcpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfc3RlcCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhlcyA9IGMoIngiLCAieSIpKSkNCmBgYA0KDQoNCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5HcmlkIHNlYXJjaCBhbGdvcml0aG08L3U+PC9zcGFuPg0KLS0tDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij5GdW5jdGlvbjwvc3Bhbj4gOiBgc2V0R3JpZFBhcmFtZXRlcl9NaXhgDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBgbWluX2FscGhhYCA6IG1pbmludW0gdmFsdWUgb2YgJFxhbHBoYSQgaW4gdGhlIGdyaWQuIEJ5IGRlZnVhbHQsIGBtaW5fYWxwaGEgPSAxZS01YC4NCiAgICAtIGBtYXhfYWxwaGFgIDogbWF4aW51bSB2YWx1ZSBvZiAkXGFscGhhJCBpbiB0aGUgZ3JpZC4gQnkgZGVmdWFsdCwgYG1heF9hbHBoYSA9IDVgLg0KICAgIC0gYG1pbl9iZXRhYCA6IG1pbmludW0gdmFsdWUgb2YgJFxiZXRhJCBpbiB0aGUgZ3JpZC4gQnkgZGVmdWFsdCwgYG1pbl9iZXRhID0gMC4xYC4NCiAgICAtIGBtYXhfYmV0YWAgOiBtYXhpbXVtIHZhbHVlIG9mICRcYmV0YSQgaW4gdGhlIGdyaWQuIEJ5IGRlZnVhbHQsIGBtYXhfYWxwaGEgPSA1MGAuDQogICAgLSBgbl9hbHBoYWAsIGBuX2JldGEgPSAzMGAgOiB0aGUgbnVtYmVyIG9mICRcYWxwaGEkIGFuZCAkXGJldGEkIHJlc3BlY3RpdmVseSBpbiB0aGUgZ3JpZC4gQnkgZGVmdWFsdCwgYG5fYWxwaGEgPSBuX2JldGEgPSAzMGAuDQogICAgLSBgcGFyYW1ldGVyc2AgOiB0aGUgbGlzdCBvZiBwYXJhbWV0ZXIgJGFscGhhJCBhbmQgJFxiZXRhJCBpbiBjYXNlIG5vbi11bmlmb3JtIGdyaWQgaXMgY29uc2lkZXJlZC4gSXQgc2hvdWxkIGJlIGEgbGlzdCBvZiB0d28gdmVjdG9ycyBjb250YWluaW5nIHRoZSB2YWx1ZXMgb2YgJFxhbHBoYSQgYW5kICRcYmV0YSQgcmVzcGVjdGl2ZWx5LiBCeSBkZWZhdWx0LCBgcGFyYW1ldGVycyA9IE5VTExgIGFuZCB0aGUgZGVmYXVsdCB1bmlmb3JtIGdyaWQgaXMgdXNlZC4NCiAgICAgLSBgYXhlc2AgOiBuYW1lcyBvZiAkeCx5JCBhbmQgJHokLWF4aXMgcmVzcGVjdGl2ZWx5LiBCeSBkZWZhdWx0LCBgYXhlcyA9IGMoImFscGhhIiwgImJldGEiLCAiUmlzayIpYC4NCiAgICAtIGB0aXRsZWAgOiB0aGUgdGl0bGUgb2YgdGhlIHBsb3QuIEJ5IGRlZmF1bHQsIGB0aXRsZSA9IE5VTExgIGFuZCB0aGUgZGVmYXVsdCB0aXRsZSBpcyBgQ3Jvc3MtdmFsaWRhdGlvbiByaXNrIGFzIGEgZnVuY3Rpb24gb2ZgICQoXGFscGhhLCBcYmV0YSkkLg0KICAgIC0gYHByaW50X3Jlc3VsdGAgOiBhIGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIHRvIHByaW50IHRoZSBvYnNlcnZlZCByZXN1bHQgb3Igbm90Lg0KICAgIC0gYGZpZ3VyZWAgOiBhIGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIHRvIHBsb3QgdGhlIGdyYXBoaWMgb2YgY3Jvc3MtdmFsaWRhdGlvbiBlcnJvciBvciBub3QuDQoNCi0gKipWYWx1ZSoqOg0KDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKmxpc3QqIG9mIGFsbCB0aGUgcGFyYW1ldGVycyBnaXZlbiBpbiBpdHMgYXJndW1lbnRzLg0KDQpgYGB7cn0NCnNldEdyaWRQYXJhbWV0ZXJfTWl4IDwtIGZ1bmN0aW9uKG1pbl9hbHBoYSA9IDFlLTUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9hbHBoYSA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9iZXRhID0gMC4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYmV0YSA9IDUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2FscGhhID0gMzAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fYmV0YSA9IDMwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhlcyA9IGMoImFscGhhIiwgImJldGEiLCAiUmlzayIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Jlc3VsdCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpZ3VyZSA9IFRSVUUpew0KICByZXR1cm4obGlzdChtaW5fYWxwaGEgPSBtaW5fYWxwaGEsDQogICAgICAgICAgICAgIG1heF9hbHBoYSA9IG1heF9hbHBoYSwNCiAgICAgICAgICAgICAgbWluX2JldGEgPSBtaW5fYmV0YSwNCiAgICAgICAgICAgICAgbWF4X2JldGEgPSBtYXhfYmV0YSwNCiAgICAgICAgICAgICAgbl9hbHBoYSA9IG5fYWxwaGEsDQogICAgICAgICAgICAgIG5fYmV0YSA9IG5fYmV0YSwNCiAgICAgICAgICAgICAgYXhlcyA9IGF4ZXMsDQogICAgICAgICAgICAgIHRpdGxlID0gdGl0bGUsDQogICAgICAgICAgICAgIHBhcmFtZXRlcnMgPSBwYXJhbWV0ZXJzLA0KICAgICAgICAgICAgICBwcmludF9yZXN1bHQgPSBwcmludF9yZXN1bHQsDQogICAgICAgICAgICAgIGZpZ3VyZSA9IGZpZ3VyZSkpDQp9DQpgYGANCg0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+RnVuY3Rpb248L3NwYW4+IDogYGdyaWRPcHRpbWl6ZXJfTWl4YA0KDQotICoqQXJndW1lbnQqKjoNCiAgICANCiAgICAtIGBvYmpfZnVuYCA6IHRoZSBvYmplY3RpdmUgZnVuY3Rpb24gZm9yIHdoaWNoIGl0cyBtaW5pbWl6ZXIgaXMgdG8gYmUgZXN0aW1hdGVkLiBJdCBzaG91bGQgYmUgYSB1bml2YXJhdGUgZnVuY3Rpb24gb2YgcmVhbCBwb3NpdGl2ZSB2YXJpYWJsZXMuDQogICAgLSBgc2V0UGFyYW1ldGVyYCA6IHRoZSBjb250cm9sIG9mIGdyaWQgc2VhcmNoIGFsZ29yaXRobSBwYXJhbWV0ZXJzIHdoaWNoIHNob3VsZCBiZSB0aGUgZnVuY3Rpb24gYHNldEdyaWRQYXJhbWV0ZXJfTWl4KClgIGRlZmluZWQgYWJvdmUuDQoNCi0gKipWYWx1ZSoqOg0KDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgbGlzdCBvZiB0aGUgZm9sbG93aW5nIG9iamVjdHM6DQoNCiAgICAtIGBvcHRfcGFyYW1gIDogdGhlIG9ic2VydmVkIHZhbHVlIG9mIHRoZSBtaW5pbWl6ZXIuDQogICAgLSBgb3B0X2Vycm9yYCA6IHRoZSB2YWx1ZSBvZiBvcHRpbWFsIHJpc2suDQogICAgLSBgYWxsX3Jpc2tgIDogdGhlIHZlY3RvciBvZiBhbGwgdGhlIGVycm9ycyBldmFsdWF0ZWQgYXQgYWxsIHRoZSB2YWx1ZXMgb2YgY29uc2lkZXJlZCBwYXJhbWV0ZXJzLg0KICAgIC0gYHJ1bi50aW1lYCA6IHRoZSBydW5uaW5nIHRpbWUgb2YgdGhlIGFsZ29yaXRobS4gDQoNCmBgYHtyfQ0KZ3JpZE9wdGltaXplcl9NaXggPC0gZnVuY3Rpb24ob2JqX2Z1bmMsDQogICAgICAgICAgICAgICAgICAgICAgICAgc2V0UGFyYW1ldGVyID0gc2V0R3JpZFBhcmFtZXRlcl9NaXgoKSl7DQogIHQwIDwtIFN5cy50aW1lKCkNCiAgaWYoaXMubnVsbChzZXRQYXJhbWV0ZXIkcGFyYW1ldGVycykpew0KICAgIHBhcmFtX2xpc3QgPC0gbGlzdChhbHBoYSA9ICByZXAoc2VxKHNldFBhcmFtZXRlciRtaW5fYWxwaGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciRtYXhfYWxwaGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoLm91dCA9IHNldFBhcmFtZXRlciRuX2FscGhhKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIkbl9iZXRhKSwNCiAgICAgICAgICAgICAgICAgICAgICAgYmV0YSA9ICByZXAoc2VxKHNldFBhcmFtZXRlciRtaW5fYmV0YSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIkbWF4X2JldGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgub3V0ID0gc2V0UGFyYW1ldGVyJG5fYmV0YSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVhY2ggPSBzZXRQYXJhbWV0ZXIkbl9hbHBoYSkpDQogIH0gZWxzZXsNCiAgICBwYXJhbV9saXN0IDwtIGxpc3QoYWxwaGEgPSByZXAoc2V0UGFyYW1ldGVyJHBhcmFtZXRlcnNbWzFdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChzZXRQYXJhbWV0ZXIkcGFyYW1ldGVyc1tbMl1dKSksDQogICAgICAgICAgICAgICAgICAgICAgIGJldGEgPSByZXAoc2V0UGFyYW1ldGVyJHBhcmFtZXRlcnNbWzJdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVhY2ggPSBsZW5ndGgoc2V0UGFyYW1ldGVyJHBhcmFtZXRlcnNbWzFdXSkpKQ0KICB9DQogIHJpc2sgPC0gbWFwMl9kYmwoLnggPSBwYXJhbV9saXN0JGFscGhhLA0KICAgICAgICAgICAgICAgICAgIC55ID0gcGFyYW1fbGlzdCRiZXRhLA0KICAgICAgICAgICAgICAgICAgIC5mID0gfiBvYmpfZnVuYyhjKC54LCAueSkpKQ0KICBpZF9vcHQgPC0gd2hpY2gubWluKHJpc2spDQogIG9wdF9lcCA8LSBjKHBhcmFtX2xpc3QkYWxwaGFbaWRfb3B0XSwgcGFyYW1fbGlzdCRiZXRhW2lkX29wdF0pDQogIG9wdF9yaXNrIDwtIHJpc2tbaWRfb3B0XQ0KICBpZihzZXRQYXJhbWV0ZXIkcHJpbnRfcmVzdWx0KXsNCiAgICBjYXQoIlxuKiBHcmlkIHNlYXJjaCBhbGdvcml0aG0uLi4iLCAiXG4gfiBPYnNlcnZlZCBwYXJhbWV0ZXI6IChhbHBoYSwgYmV0YSkgPSAoIiwgb3B0X2VwWzFdLCANCiAgICAgICAgIiwgIiwgDQogICAgICAgIG9wdF9lcFsyXSwgIikiLCANCiAgICAgICAgc2VwID0gIiIpDQogIH0NCiAgaWYoc2V0UGFyYW1ldGVyJGZpZ3VyZSl7DQogICAgaWYoaXMubnVsbChzZXRQYXJhbWV0ZXIkdGl0bGUpKXsNCiAgICAgIHRpdCA8LSBwYXN0ZSgiPGI+IENyb3NzLXZhbGlkYXRpb24gcmlzayBhcyBhIGZ1bmN0aW9uIG9mPC9iPiAoIiwNCiAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIkYXhlc1sxXSwiLCIsIA0KICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciRheGVzWzJdLA0KICAgICAgICAgICAgICAgICAgICIpIikNCiAgICB9IGVsc2V7DQogICAgICB0aXQgPC0gc2V0UGFyYW1ldGVyJHRpdGxlDQogICAgfQ0KICAgIGZpZyA8LSB0aWJibGUoYWxwaGEgPSBwYXJhbV9saXN0JGFscGhhLCANCiAgICAgICAgICAgICAgICAgIGJldGEgPSBwYXJhbV9saXN0JGJldGEsDQogICAgICAgICAgICAgICAgICByaXNrID0gcmlzaykgJT4lDQogICAgICBwbG90X2x5KHggPSB+YWxwaGEsIHkgPSB+YmV0YSwgeiA9IH5yaXNrLCB0eXBlID0gIm1lc2gzZCIpICU+JQ0KICAgICAgYWRkX3RyYWNlKHggPSBjKG9wdF9lcFsxXSwgb3B0X2VwWzFdKSwNCiAgICAgICAgICAgICAgICB5ID0gYygwLCBvcHRfZXBbMl0pLA0KICAgICAgICAgICAgICAgIHogPSBjKG9wdF9yaXNrLCBvcHRfcmlzayksDQogICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLA0KICAgICAgICAgICAgICAgIG1vZGUgPSAnbGluZXMrbWFya2VycycsDQogICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoIA0KICAgICAgICAgICAgICAgICAgd2lkdGggPSAyLA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSAiIzVFODhGQyIsIA0KICAgICAgICAgICAgICAgICAgZGFzaCA9IFRSVUUpLA0KICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoDQogICAgICAgICAgICAgICAgICBzaXplID0gNCwNCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gfmMoIiM1RTg4RkMiLCAiIzM4REUyNSIpKSwNCiAgICAgICAgICAgICAgICBuYW1lID0gcGFzdGUoIk9wdGltYWwiLHNldFBhcmFtZXRlciRheGVzWzFdKSkgJT4lDQogICAgICBhZGRfdHJhY2UoeCA9IGMoMCwgb3B0X2VwWzFdKSwNCiAgICAgICAgICAgICAgICB5ID0gYyhvcHRfZXBbMl0sIG9wdF9lcFsyXSksDQogICAgICAgICAgICAgICAgeiA9IGMob3B0X3Jpc2ssIG9wdF9yaXNrKSwNCiAgICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsDQogICAgICAgICAgICAgICAgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywNCiAgICAgICAgICAgICAgICBsaW5lID0gbGlzdCggDQogICAgICAgICAgICAgICAgICB3aWR0aCA9IDIsDQogICAgICAgICAgICAgICAgICBjb2xvciA9ICIjRjMxNTM2IiwgDQogICAgICAgICAgICAgICAgICBkYXNoID0gVFJVRSksDQogICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdCgNCiAgICAgICAgICAgICAgICAgIHNpemUgPSA0LA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSB+YygiI0YzMTUzNiIsICIjMzhERTI1IikpLA0KICAgICAgICAgICAgICAgIG5hbWUgPSBwYXN0ZSgiT3B0aW1hbCIsc2V0UGFyYW1ldGVyJGF4ZXNbMl0pKSAgJT4lDQogICAgICBhZGRfdHJhY2UoeCA9IG9wdF9lcFsxXSwNCiAgICAgICAgICAgICAgICB5ID0gb3B0X2VwWzJdLA0KICAgICAgICAgICAgICAgIHogPSBvcHRfcmlzaywNCiAgICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsDQogICAgICAgICAgICAgICAgbW9kZSA9ICdtYXJrZXJzJywNCiAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAgc2l6ZSA9IDUsDQogICAgICAgICAgICAgICAgICBjb2xvciA9ICIjMzhERTI1IiksDQogICAgICAgICAgICAgICAgbmFtZSA9ICJPcHRpbWFsIHBvaW50IikgJT4lDQogICAgICBsYXlvdXQodGl0bGUgPSBsaXN0KHRleHQgPSB0aXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHggPSAwLjA3NSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAwLjkyNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZm9udCA9IGxpc3QoZmFtaWx5ID0gIlZlcmRhbmEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICIjNUU4OEZDIikpLA0KICAgICAgICAgICAgIGxlZ2VuZCA9IGxpc3QoeCA9IDEwMCwgeSA9IDAuNSksDQogICAgICAgICAgICAgc2NlbmUgPSBsaXN0KHhheGlzID0gbGlzdCh0aXRsZSA9IHNldFBhcmFtZXRlciRheGVzWzFdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gc2V0UGFyYW1ldGVyJGF4ZXNbMl0pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB6YXhpcyA9IGxpc3QoIHRpdGxlID0gc2V0UGFyYW1ldGVyJGF4ZXNbM10pKSkNCiAgICBwcmludChmaWcpDQogIH0NCiAgdDEgPC0gU3lzLnRpbWUoKQ0KICByZXR1cm4obGlzdChvcHRfcGFyYW0gPSBvcHRfZXAsDQogICAgICAgICAgICAgIG9wdF9lcnJvciA9IG9wdF9yaXNrLA0KICAgICAgICAgICAgICBhbGxfcmlzayA9IHJpc2ssDQogICAgICAgICAgICAgIHJ1bi50aW1lID0gZGlmZnRpbWUodDEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgdDAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgdW5pdHMgPSAic2VjcyIpW1sxXV0pDQogICkNCn0NCmBgYA0KDQoNCi0tLQ0KDQo+ICoqRXhhbXBsZS4yKio6IEFnYWluIHdpdGggZ3JpZCBzZWFyY2guDQoNCi0tLQ0KDQpgYGB7ciwgb3V0LndpZHRoPSc5MCUnLCBmaWcuYWxpZ249J2NlbnRlcid9DQpncmlkIDwtIGdyaWRPcHRpbWl6ZXJfTWl4KG9ial9mdW4gPSBvYmplY3RfZnVuYywNCiAgICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciA9IHNldEdyaWRQYXJhbWV0ZXJfTWl4KG1pbl9hbHBoYSA9IC0yLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYWxwaGEgPSA0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fYmV0YSA9IC0yLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYmV0YSA9IDQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fYWxwaGEgPSAxNTAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fYmV0YSA9IDE1MCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhlcyA9IGMoIngiLCAieSIsICJ6IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gInogPSBmKHgseSkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFKSkNCmBgYA0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT4kXGthcHBhJC1jcm9zcyB2YWxpZGF0aW9uIGxvc3QgZnVuY3Rpb248L3U+PC9zcGFuPg0KLS0tDQoNCkNvbnN0cnVjdGluZyBhZ2dyZWdhdGlvbiBtZXRob2QgaXMgZXF1aXZhbGVudCB0byBhcHByb3hpbWF0aW5nIHRoZSBvcHRpbWFsIHZhbHVlIG9mIHBhcmFtZXRlciAkKFxhbHBoYSxcYmV0YSlcaW4oXG1hdGhiYntSfV8rXiopXjIkIGludHJvZHVjZWQgaW4gc2VjdGlvbiAxLjEgYnkgbWluaW1pemluZyBzb21lIGxvc3QgZnVuY3Rpb24uIEluIHRoaXMgc3R1ZHksIHdlIHByb3Bvc2UgJFxrYXBwYSQtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIGxvc3QgZnVuY3Rpb24gZGVmaW5lZCBieQ0KDQpcYmVnaW57ZXF1YXRpb259DQpcbGFiZWx7ZXE6a2FwcGF9DQpcdmFycGhpXntca2FwcGF9KGgpPVxmcmFjezF9e1xrYXBwYX1cc3VtX3trPTF9Xntca2FwcGF9XHN1bV97KHhfaix5X2opXGluIEZfa30oZ19uKHhfaikteV9qKV4yDQpcZW5ke2VxdWF0aW9ufQ0Kd2hlcmUNCg0KLSBmb3IgYW55ICRrPTEsLi4uLFxrYXBwYSQsICRGX2skIGRlbm90ZXMgdGhlICRrJHRoIHZhbGlkYXRpb24gZm9sZC4NCi0gJGdfbih7XGJmIHJ9KHhfaikpJCBpcyB0aGUgcHJlZGljdGlvbiBvZiAkeF9qJCBvZiAkRl9rJCwgY29tcHV0ZWQgdXNpbmcgdGhlIGRhdGEgcG9pbnRzIGZyb20gdGhlIHJlbWFpbmluZyBwYXJ0ICR7XGNhbCBEfV97XGVsbH0tRl9rJCBieSwNCiQkZ19uKHtcYmYgcn0oeF9qKSk9XGZyYWN7XHN1bV97KHhfaSx5X2kpXGlue1xjYWwgRH1fe1xlbGx9LUZfa315X2lLX3tcYWxwaGEsIFxiZXRhfSh4X2oteF9pLHtcYmYgcn0oeF9qKS0ge1xiZiByfSh4X2kpKX17XHN1bV97KHhfaSx5X2kpXGlue1xjYWwgRH1fe1xlbGx9LUZfa31LX3tcYWxwaGEsIFxiZXRhfSh4X2oteF9pLHtcYmYgcn0oeF9qKS0ge1xiZiByfSh4X2kpKX0kJA0KDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPjogYGRpc3RfbWF0cml4X01peGANCi0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGNvbXB1dGVzIGRpZmZlcmVudCBkaXN0YW5jZXMgYmV0d2VlbiBkYXRhIHBvaW50cyBvZiBlYWNoIHRyYWluaW5nIGZvbGRzICgkXG1hdGhjYWx7RH1fe1xlbGx9LUZfayQpIGFuZCB0aGUgY29ycmVzcG9uZGluZyB2YWxpZGF0aW9uIGZvbGQgJEZfayQgZm9yIGFueSAkaz0xLFxkb3RzLFxrYXBwYSQuIFRoZSAkXGthcHBhJCBkaXN0YW5jZSBtYXRyaWNlcyAkRF9rPShkW3tcYmYgcn0oeF9pKSx7XGJmIHJ9KHhfaildKV97aSxqfSQgZm9yICRrPTEsXGRvdHMsXGthcHBhJCwgYXJlIGNvbXB1dGVkLCB3aGVyZSB0aGUgZGlzdGFuY2UgJGQkIGlzIGRlZmluZWQgYWNjb3JkaW5nIHRvIGRpZmZlcmVudCB0eXBlcyBrZXJuZWwgZnVuY3Rpb25zLg0KDQotICoqQXJndW1lbnQqKjoNCiAgICANCiAgICAtIGBiYXNpY01hY2hpbmVzYCA6IHRoZSBiYXNpYyBtYWNoaW5lIG9iamVjdCwgd2hpY2ggaXMgYW4gb3V0cHV0IG9mIGBnZW5lcmF0ZU1hY2hpbmVzX01peGAgZnVuY3Rpb24uDQogICAgLSBgbl9jdmAgOiB0aGUgbnVtYmVyICRca2FwcGEkIG9mIGNyb3NzLXZhbGlkYXRpb24gZm9sZHMuIEJ5IGRlZmF1bHQsIGBuX2N2ID0gNWAuDQogICAgLSBga2VybmVsYCA6IHRoZSBrZXJuZWwgZnVuY3Rpb24gdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLCB3aGljaCBpcyBhbiBlbGVtZW50IG9mIHsiZ2F1c3NpYW4iLCAiZXBhbmVjaG5pa292IiwgImJpd2VpZ2h0IiwgInRyaXdlaWdodCIsICJ0cmlhbmd1bGFyIiwgIm5haXZlIn0uIEJ5IGRlZmF1bHQsIGBrZXJuZWwgPSAiZ2F1c3NpYW4iYC4NCiAgICANCi0gKipWYWx1ZSoqOg0KICAgIA0KICAgIFRoaXMgZnVuY3Rpb25zIHJldHVybnMgYSAqbGlzdCogb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgIA0KICAgIC0gYGRpc3RgIDogYSBsaXN0IG9mIGRhdGEgZnJhbWUgKGB0aWJibGVgKSBjb250YWluaW5nIHN1Ymxpc3RzIGNvcnJlc3BvbmRpbmcgdG8ga2VybmVsIGZ1bmN0aW9ucyB1c2VkIGZvciB0aGUgYWdncmVnYXRpb24uIEVhY2ggc3VibGlzdCBjb250YWlucyBgbl9jdmAgbnVtYmVycyBvZiBkaXN0YW5jZSBtYXRyaWNlcyAkRF9rPShkW3tcYmYgcn0oeF9pKSx7XGJmIHJ9KHhfaildKV97aSxqfSQsIGZvciAkaz0xLFxkb3RzLFxrYXBwYSQsIGNvbnRhaW5pbmcgZGlzdGFuY2VzIGJldHdlZW4gdGhlIGRhdGEgcG9pbnRzIGluIHZhbGlhdGlvbiBmb2xkIChhbG9uZyB0aGUgY29sdW1ucykgYW5kIHRoZSAkMS1ca2FwcGEkIHJlbWFpbmluZyBmb2xkcyBvZiB0cmFpbmluZyBkYXRhIChhbG9uZyB0aGUgcm93cykuIFRoZSB0eXBlIG9mIGRpc3RhbmNlIG1hdHJpY2VzIGRlcGVuZHMgb24gdGhlIGtlcm5lbCB1c2VkOg0KICAgICAgICAtIElmIGBrZXJuZWwgPSBuYWl2ZWAsIHRoZSBkaXN0YW5jZSBtYXRyaWNlcyBjb250YWluIHRoZSBtYXhpbXVtIGRpc3RhbmNlIGJldHdlZW4gZGF0YSBwb2ludHMsIGkuZS4sICQkRF9rPShcfHtcYmYgcn0oeF9pKS17XGJmIHJ9KHhfailcfF97XG1heH0pX3tpLGp9XHRleHR7IGZvciB9az0xLFxkb3RzLFxrYXBwYS4kJA0KICAgICAgICAtIElmIGBrZXJuZWwgPSB0cmlhbmd1bGFyYCwgdGhlIGRpc3RhbmNlIG1hdHJpY2VzIGNvbnRhaW4gdGhlICRMXzEkIGRpc3RhbmNlIGJldHdlZW4gZGF0YSBwb2ludHMsIGkuZS4sIGBkaXN0X21hdHJpeF9NaXhgICQkRF9rPShcfHtcYmYgcn0oeF9pKS17XGJmIHJ9KHhfailcfF8xKV97aSxqfVx0ZXh0eyBmb3IgfWs9MSxcZG90cyxca2FwcGEuJCQNCiAgICAgICAgLSBPdGhlcndpc2UsIHRoZSBkaXN0YW5jZSBtYXRyaWNlcyBjb250YWluIHRoZSBzcXVhcmVkICRMXzIkIGRpc3RhbmNlIGJldHdlZW4gZGF0YSBwb2ludHMsIGkuZS4sICQkRF9rPShcfHtcYmYgcn0oeF9pKS17XGJmIHJ9KHhfailcfF8gMl4yKV97aSxqfVx0ZXh0eyBmb3IgfWs9MSxcZG90cyxca2FwcGEuJCQNCiAgICAtIGBpZF9zaHVmZmxlYCA6IHRoZSBzaHVmZmxlZCBpbmRpY2VzIGluIGNyb3NzLXZhbGlkYXRpb24uDQogICAgLSBgbl9jdmAgOiB0aGUgbnVtYmVyICRca2FwcGEkIG9mIGNyb3NzLXZhbGlkYXRpb24gZm9sZHMuDQogICAgDQpgYGB7cn0NCmRpc3RfbWF0cml4X01peCA8LSBmdW5jdGlvbihiYXNpY01hY2hpbmVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgbl9jdiA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWwgPSAiZ2F1c2lhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICBpZF9zaHVmZmxlID0gTlVMTCl7DQogIG4gPC0gbnJvdyhiYXNpY01hY2hpbmVzJGZpdHRlZF9yZW1haW4pDQogIG5fZWFjaF9mb2xkIDwtIGZsb29yKG4vbl9jdikNCiAgIyBzaHVmZmxlZCBpbmRpY2VzDQogIGlmKGlzLm51bGwoaWRfc2h1ZmZsZSkpew0KICAgIHNodWZmbGUgPC0gMToobl9jdi0xKSAlPiUNCiAgICByZXAobl9lYWNoX2ZvbGQpICU+JQ0KICAgIGMoLiwgcmVwKG5fY3YsIG4gLSBuX2VhY2hfZm9sZCAqIChuX2N2IC0gMSkpKSAlPiUNCiAgICBzYW1wbGUNCiAgfWVsc2V7DQogICAgc2h1ZmZsZSA8LSBpZF9zaHVmZmxlDQogIH0NCiAgIyB0aGUgcHJlZGljdGlvbiBtYXRyaXggRF9sDQogIGRmX21hY2ggPC0gYXMubWF0cml4KGJhc2ljTWFjaGluZXMkZml0dGVkX3JlbWFpbikNCiAgZGZfaW5wdXQgPC0gYXMubWF0cml4KGJhc2ljTWFjaGluZXMkdHJhaW5fZGF0YSR0cmFpbl9pbnB1dFtiYXNpY01hY2hpbmVzJGlkMixdKQ0KICBpZighIChrZXJuZWwgJWluJSBjKCJuYWl2ZSIsICJ0cmlhbmd1bGFyIikpKXsNCiAgICBwYWlyX2Rpc3QgPC0gZnVuY3Rpb24oTSwgTil7DQogICAgICBuX04gPC0gZGltKE4pDQogICAgICBuX00gPC0gZGltKE0pDQogICAgICByZXNfIDwtIDE6bnJvdyhOKSAlPiUNCiAgICAgICAgbWFwX2RmYyguZiA9IChcKGlkKSB0aWJibGUoJ3t7aWR9fScgOj0gYXMudmVjdG9yKHJvd1N1bXMoKE0gLSBtYXRyaXgocmVwKE5baWQsXSwgbl9NWzFdKSwgbmNvbCA9IG5fTVsyXSwgYnlyb3cgPSBUUlVFKSleMikpKSkpDQogICAgICByZXR1cm4ocmVzXykNCiAgICB9DQogIH0NCiAgaWYoa2VybmVsID09ICJ0cmlhbmd1bGFyIil7DQogICAgcGFpcl9kaXN0IDwtIGZ1bmN0aW9uKE0sIE4pew0KICAgICAgbl9OIDwtIGRpbShOKQ0KICAgICAgbl9NIDwtIGRpbShNKQ0KICAgICAgcmVzXyA8LSAxOm5yb3coTikgJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3Rvcihyb3dTdW1zKGFicyhNIC0gbWF0cml4KHJlcChOW2lkLF0sIG5fTVsxXSksIG5jb2wgPSBuX01bMl0sIGJ5cm93ID0gVFJVRSkpKSkpKSkNCiAgICAgIHJldHVybihyZXNfKQ0KICAgIH0NCiAgfQ0KICBpZihrZXJuZWwgPT0gIm5haXZlIil7DQogICAgcGFpcl9kaXN0IDwtIGZ1bmN0aW9uKE0sIE4pew0KICAgICAgbl9OIDwtIGRpbShOKQ0KICAgICAgbl9NIDwtIGRpbShNKQ0KICAgICAgcmVzXyA8LSAxOm5yb3coTikgJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3RvcihhcHBseShhYnMoTSAtIG1hdHJpeChyZXAoTltpZCxdLCBuX01bMV0pLCBuY29sID0gbl9NWzJdLCBieXJvdyA9IFRSVUUpKSwgMSwgbWF4KSkpKSkNCiAgICAgIHJldHVybihyZXNfKQ0KICAgIH0NCiAgfQ0KICBMMSA8LSAxOm5fY3YgJT4lDQogICAgICBtYXAoLmYgPSAoXCh4KSBwYWlyX2Rpc3QoZGZfaW5wdXRbc2h1ZmZsZSAhPSB4LF0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmX2lucHV0W3NodWZmbGUgPT0geCxdKSkpDQogIEwyIDwtIDE6bl9jdiAlPiUNCiAgICAgIG1hcCguZiA9IChcKHgpIHBhaXJfZGlzdChkZl9tYWNoW3NodWZmbGUgIT0geCxdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9tYWNoW3NodWZmbGUgPT0geCxdKSkpDQogIHJldHVybihsaXN0KGRpc3RfaW5wdXQgPSBMMSwNCiAgICAgICAgICAgICAgZGlzdF9tYWNoaW5lID0gTDIsDQogICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBzaHVmZmxlLA0KICAgICAgICAgICAgICBuX2N2ID0gbl9jdikpDQp9DQpgYGANCg0KDQotLS0NCg0KPiAqKkV4YW1wbGUuMyoqOiBUaGUgbWV0aG9kIGBkaXN0X21hdHJpeF9NaXhgIGlzIGltcGxlbWVudGVkIG9uIHRoZSBvYnRhaW5lZCBiYXNpYyBtYWNoaW5lcyBidWlsdCBpbiAqRXhhbXBsZS4xKiB3aXRoIHRoZSBjb3JyZXNwb25kaW5nIEdhdXNzaWFuIGtlcm5lbCBmdW5jdGlvbi4NCg0KLS0tDQoNCmBgYHtyfQ0KZGlzIDwtIGRpc3RfbWF0cml4X01peChiYXNpY01hY2hpbmVzID0gYmFzaWNfbWFjaGluZXMsDQogICAgICAgICAgICBuX2N2ID0gMywNCiAgICAgICAgICAgIGtlcm5lbCA9ICJnYXVzc2lhbiIpDQpkaXMkbl9jdg0KYGBgDQoNCi0tLQ0KDQo+ICoqRXhhbXBsZS40Kio6IEZyb20gdGhlIGRpc3RhbmNlIG1hdHJpeCwgd2UgY2FuIGNvbXB1dGUgdGhlIGVycm9yIGNvcnJlc3BvbmRpbmcgdG8gR2F1c3NpYW4ga2VybmVsIGZ1bmN0aW9uLCB0aGVuIHVzZSBib3RoIG9mIHRoZSBvcHRpbWl6YXRpb24gbWV0aG9kcyB0byBhcHByb3hpbWF0ZSB0aGUgc21vb3RoaW5nIHBhcmFtdGVyIGluIHRoaXMgY2FzZS4NCg0KLS0tDQoNCmBgYHtyfQ0KIyBHYXVzc2lhbiBrZXJuZWwNCmdhdXNzaWFuX2tlcm4gPC0gZnVuY3Rpb24oLmVwID0gYyguMDUsIDAuMDA1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAuaW52X3NpZ21hID0gc3FydCguNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIC5hbHBoYSA9IDIpew0KICBrZXJuX2Z1biA8LSBmdW5jdGlvbih4LCBpZCwgRDEsIEQyKXsNCiAgICB0ZW0wIDwtIGFzLm1hdHJpeChleHAoLSAoeFsxXSpEMSt4WzJdKkQyKV4oLmFscGhhLzIpKi5pbnZfc2lnbWFeLmFscGhhKSkNCiAgICB5X2hhdCA8LSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlICE9IGlkXSAlKiUgdGVtMC9jb2xTdW1zKHRlbTApDQogICAgcmV0dXJuKHN1bSgoeV9oYXQgLSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlID09IGlkXSleMikpDQogIH0NCiAgdGVtcCA8LSBtYXAoLnggPSAxOi5kaXN0X21hdHJpeCRuX2N2LCANCiAgICAgICAgICAgICAgLmYgPSB+IGtlcm5fZnVuKHggPSAuZXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQxID0gLmRpc3RfbWF0cml4JGRpc3RfaW5wdXRbWy54XV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDIgPSAuZGlzdF9tYXRyaXgkZGlzdF9tYWNoaW5lW1sueF1dKSkNCiAgcmV0dXJuKFJlZHVjZSgiKyIsIHRlbXApKQ0KfQ0KDQojIEthcHBhIGNyb3NzLXZhbGlkYXRpb24gZXJyb3INCmNvc3RfZnVuIDwtIGZ1bmN0aW9uKHgsDQogICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXggPSBkaXMsDQogICAgICAgICAgICAgICAgICAgICAua2VybmVsX2Z1bmMgPSBnYXVzc2lhbl9rZXJuLA0KICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMiA9IGJhc2ljX21hY2hpbmVzJHRyYWluX2RhdGEkdHJhaW5fcmVzcG9uc2VbYmFzaWNfbWFjaGluZXMkaWQyXSwNCiAgICAgICAgICAgICAgICAgICAgIC5pbnZfc2lnbWEgPSBzcXJ0KC41KSwNCiAgICAgICAgICAgICAgICAgICAgIC5hbHBoYSA9IDIpew0KICByZXR1cm4oLmtlcm5lbF9mdW5jKC5lcCA9IHgsDQogICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4ID0gLmRpc3RfbWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIgPSAudHJhaW5fcmVzcG9uc2UyLA0KICAgICAgICAgICAgICAgICAgICAgIC5pbnZfc2lnbWEgPSAuaW52X3NpZ21hLA0KICAgICAgICAgICAgICAgICAgICAgIC5hbHBoYSA9IC5hbHBoYSkpDQp9DQpgYGANCg0KLSAqKkdyYWRpZW50IGRlc2NlbnQqKg0KDQpgYGB7ciwgb3V0LndpZHRoPSc5MCUnLCBmaWcuYWxpZ249J2NlbnRlcid9DQojIE9wdGltaXphdGlvbg0Kb3B0X3BhcmFtX2dkIDwtIGdyYWRPcHRpbWl6ZXJfTWl4KG9ial9mdW4gPSBjb3N0X2Z1biwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KHJhdGUgPSAibGluZWFyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfc3RlcCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Jlc3VsdCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpZ3VyZSA9IFRSVUUpKQ0KYGBgDQoNCi0gKipHcmlkIHNlYXJjaCoqDQoNCmBgYHtyLCBvdXQud2lkdGg9JzkwJScsIGZpZy5hbGlnbj0nY2VudGVyJ30NCiMgT3B0aW1pemF0aW9uDQpvcHRfcGFyYW1fZ3JpZCA8LSBncmlkT3B0aW1pemVyX01peChvYmpfZnVuID0gY29zdF9mdW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciA9IHNldEdyaWRQYXJhbWV0ZXJfTWl4KG1pbl9hbHBoYSA9IDAuMDAwMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYWxwaGEgPSAyMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fYmV0YSA9IDAuMDAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9iZXRhID0gMTAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fYmV0YSA9IDMwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fYWxwaGEgPSAzMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFKSkNCmBgYA0KDQpgYGB7cn0NCmNhdCgnKiBPYnNlcnZlZCBwYXJhbWV0ZXI6XG5cdCAtIEdyYWRpZW50IGRlc2NlbnRcdDogKGFscGhhLCBiZXRhKSA9ICgnLCANCiAgICBvcHRfcGFyYW1fZ2Qkb3B0X3BhcmFtWzFdLCIsIixvcHRfcGFyYW1fZ2Qkb3B0X3BhcmFtWzJdLCAiKSIsDQogICAgJ1x0IHdpdGggZXJyb3I6JywgY29zdF9mdW4ob3B0X3BhcmFtX2dkJG9wdF9wYXJhbSksDQogICAgJ1xuXHQgLSBHcmlkIHNlYXJjaFx0XHQ6IChhbHBoYSwgYmV0YSkgPSAoJyxvcHRfcGFyYW1fZ3JpZCRvcHRfcGFyYW1bMV0sIiwiLA0KICAgIG9wdF9wYXJhbV9ncmlkJG9wdF9wYXJhbVsyXSwNCiAgICAnKSAgXHQgd2l0aCBlcnJvcjonLCBjb3N0X2Z1bihvcHRfcGFyYW1fZ3JpZCRvcHRfcGFyYW0pKQ0KYGBgDQoNCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5GaXR0aW5nIHBhcmFtZXRlcjwvdT48L3NwYW4+DQotLS0NCg0KVGhpcyBmdW5jdGlvbiBnYXRoZXJzIHRoZSBjb25zdHJ1Y3RlZCBtYWNoaW5lcyBhbmQgcGVyZm9ybSBhbiBvcHRpbWl6YXRpb24gYWxnb3JpdGhtIHRvIGFwcHJveGltYXRlIHRoZSBzbW9vdGhpbmcgcGFyYW1ldGVyIGZvciB0aGUgYWdncmVnYXRpb24sIHVzaW5nIG9ubHkgdGhlIHJlbWFpbmluZyBwYXJ0ICR7XGNhbCBEfV97XGVsbH0kIG9mIHRoZSB0cmFpbmluZyBkYXRhLg0KDQotICoqQXJndW1lbnQqKjoNCiAgICANCiAgICAtIGB0cmFpbl9pbnB1dGAsIDogYSBtYXRyaXggb3IgZGF0YSBmcmFtZSBvZiB0aGUgdHJhaW5pbmcgKmlucHV0IGRhdGEqLg0KICAgIC0gYHRyYWluX3Jlc3BvbnNlYCA6IGEgdmVjdG9yIG9mIHRoZSBjb3JyZXNwb25kaW5nIHJlc3BvbnNlIHZhcmlhYmxlIG9mIHRoZSBgdHJhaW5faW5wdXRgLg0KICAgIC0gYG1hY2hpbmVzYCA6IGEgdmVjdG9yIG9mIGJhc2ljIG1hY2hpbmVzIHRvIGJlIGNvbnN0cnVjdGVkLiBJdCBtdXN0IGJlIGEgc3Vic2V0IG9mIHsibGFzc28iLCAicmlkZ2UiLCAia25uIiwgInRyZWUiLCAicmYiLCAieGdiIn0uIEJ5IGRlZmF1bHQsIGBtYWNoaW5lcyA9IE5VTExgIGFuZCBhbGwgdGhlIHNpeCB0eXBlcyBvZiBiYXNpYyBtYWNoaW5lcyBhcmUgYnVpbHQuDQogICAgLSBgc2NhbGVfaW5wdXRgIDogYSBsb2dpY2FsIHZhbHVlIHNwZWNpZnlpbmcgd2hldGhlciBvciBub3QgdG8gc2NhbGUgdGhlIGlucHV0IGRhdGEgYmVmb3JlIGJ1aWxkaW5nIHRoZSBiYXNpYyByZWdyZXNzaW9uIHByZWRpY3RvcnMuIEJ5IGRlZmF1bHQsIGBzY2FsZV9pbnB1dCA9IFRSVUVgLg0KICAgIC0gYHNjYWxlX21hY2hpbmVgIDogYSBsb2dpY2FsIHZhbHVlIHNwZWNpZnlpbmcgd2hldGhlciBvciBub3QgdG8gc2NhbGUgdGhlIHByZWRpY3RlZCBmZWF0dXJlcyBnaXZlbiBieSBhbGwgdGhlIGJhc2ljIHJlZ3Jlc3Npb24gcHJlZGljdG9ycywgZm9yIGFnZ3JlZ2F0aW9uLiBCeSBkZWZhdWx0LCBgc2NhbGVfbWFjaGluZSA9IFRSVUVgLg0KICAgIC0gYHNwbGl0c2AgOiB0aGUgcHJvcG9ydGlvbiBvZiB0cmFpbmluZyBkYXRhICh0aGUgcHJvcG9ydGlvbiBvZiAke1xjYWwgRH1fayQgaW4gJHtcY2FsIER9X24kKSB1c2VkIHRvIGJ1aWxkIHRoZSBiYXNpYyBtYWNoaW5lcy4gQnkgZGVmYXVsdCwgYHNwbGl0cyA9IC41YC4NCiAgICAtIGBuX2N2YCA6IHRoZSBudW1iZXIgb2YgY3Jvc3MtdmFsaWRhdGlvbiBmb2xkcyB1c2VkIHRvIHR1bmUgdGhlIHNtb290aGluZyBwYXJhbWV0ZXIuDQogICAgLSBgaW52X3NpZ21hLCBhbHBoYWAgOiB0aGUgaW52ZXJzZSBub3JtYWxpemVkIGNvbnN0YW50ICRcc2lnbWFeey0xfT4wJCBhbmQgdGhlIGV4cG9uZW50ICRcYWxwaGE+MCQgb2YgZXhwb25lbnRpYWwga2VybmVsOiAkSyh4KT1lXnstXHx4L1xzaWdtYVx8XntcYWxwaGF9fSQgZm9yIGFueSAkeFxpblxtYXRoYmJ7Un1eZCQuIEJ5IGRlZmF1bHQsIGBpbnZfc2lnbWEgPSBgJFxzcXJ0ezEvMn0kIGFuZCBgYWxwaGEgPSAyYCB3aGljaCBjb3JyZXNwb25kcyB0byB0aGUgR2F1c3NpYW4ga2VybmVsLg0KICAgIC0gYGtlcm5lbHNgIDogdGhlIGtlcm5lbCBmdW5jdGlvbiBvciB2ZWN0b3Igb2Yga2VybmVsIGZ1bmN0aW9ucyB1c2VkIGZvciB0aGUgYWdncmVnYXRpb24uIEJ5IGZhdWx0LCBga2VybmVscyA9ICJnYXVzc2lhbiJgLg0KICAgIC0gYG9wdGltaXplTWV0aG9kYCA6IHRoZSBvcHRpbWl6YXRpb24gbWV0aG9kcyB1c2VkIHRvIGxlYXJuIHRoZSBzbW9vdGhpbmcgcGFyYW1ldGVyLiBCeSBkZWZhdWx0LCBgb3B0aW1pemVNZXRob2QgPSAiZ3JhZCJgIHdoaWNoIHN0YW5kcyBmb3IgZ3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG0sIGFuZCBpZiBgb3B0aW1pemVNZXRob2QgPSAiZ3JpZCJgLCB0aGVuIHRoZSBncmlkIHNlYXJjaCBhbGdvcml0aG0gaXMgdXNlZC4gPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+Kk5vdGUgdGhhdCB0aGlzIHNob3VsZCBiZSBvZiB0aGUgc2FtZSBzaXplIGFzIHRoZSo8L3NwYW4+IGBrZXJuZWxzYCA8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij4qYXJndW1lbnQsIG90aGVyd2lzZSAiZ3JpZCIgbWV0aG9kIHdpbGwgYmUgdXNlZCBmb3IgYWxsIHRoZSBrZXJuZWxzKjwvc3Bhbj4uDQogICAgLSBgc2V0QmFzaWNNYWNoaW5lUGFyYW1gIDogYW4gb3B0aW9uIHVzZWQgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtZXRlcnMgb2YgdGhlIGJhc2ljIG1hY2hpbmVzLiBgc2V0QmFzaWNQYXJhbWV0ZXJfTWl4YCBmdW5jdGlvbiBzaG91bGQgYmUgZmVkIHRvIHRoaXMgYXJndW1lbnQuDQogICAgLSBgc2V0R3JhZFBhcmFtYCA6IGFuIG9wdGlvbiB1c2VkIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzIG9mIHRoZSBncmFkaWVudCBkZXNjZW50IGFsZ29yaXRobS4gYHNldEdyYWRQYXJhbWV0ZXJfTWl4YCBmdW5jdGlvbiBzaG91bGQgYmUgZmVkIHRvIGl0Lg0KICAgIC0gYHNldEdyaWRQYXJhbWAgOiBhbiBvcHRpb24gdXNlZCB0byBzZXQgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVycyBvZiB0aGUgZ3JpZCBzZWFyY2ggYWxnb3JpdGhtLiBgc2V0R3JpZFBhcmFtZXRlcl9NaXhgIGZ1bmN0aW9uIHNob3VsZCBiZSBmZWQgdG8gaXQuDQogICAgDQotICoqVmFsdWUqKjoNCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KDQogICAgLSBgb3B0X3BhcmFtZXRlcnNgIDogdGhlIG9ic2VydmVkIG9wdGltYWwgcGFyYW1ldGVycy4NCiAgICAtIGBhZGRfcGFyYW1ldGVyc2AgOiBvdGhlciBhZGl0aW9uYWwgcGFyYW1ldGVycyBzdWNoIGFzIHNjYWxpbmcgb3B0aW9ucywgcGFyYW1ldGVycyBvZiBrZXJuZWwgZnVuY3Rpb25zIGFuZCB0aGUgb3B0aW1pemF0aW9uIG1ldGhvZHMgdXNlZC4NCiAgICAtIGBiYXNpY19tYWNoaW5lc2AgOiB0aGUgbGlzdCBvZiBiYXNpYyBtYWNoaW5lIG9iamVjdC4NCg0KYGBge3J9DQpmaXRfcGFyYW1ldGVyX01peCA8LSBmdW5jdGlvbih0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9wcmVkaWN0aW9ucyA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgIG1hY2hpbmVzID0gTlVMTCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2lucHV0ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfbWFjaGluZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IDAuNSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICBpbnZfc2lnbWEgPSBzcXJ0KC41KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwID0gMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9ICJnYXVzc2lhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIG9wdGltaXplTWV0aG9kID0gImdyYWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRCYXNpY01hY2hpbmVQYXJhbSA9IHNldEJhc2ljUGFyYW1ldGVyX01peCgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmFkUGFyYW0gPSBzZXRHcmFkUGFyYW1ldGVyX01peCgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1ldGVyX01peCgpKXsNCiAga2VybmVsc19sb29rdXAgPC0gYygiZ2F1c3NpYW4iLCAiZXBhbmVjaG5pa292IiwgImJpd2VpZ2h0IiwgInRyaXdlaWdodCIsICJ0cmlhbmd1bGFyIiwgIm5haXZlIikNCiAga2VybmVsX3JlYWwgPC0ga2VybmVscyAlPiUNCiAgICBzYXBwbHkoRlVOID0gZnVuY3Rpb24oeCkgcmV0dXJuKG1hdGNoLmFyZyh4LCBrZXJuZWxzX2xvb2t1cCkpKQ0KICBpZihpcy5udWxsKHRyYWluX3ByZWRpY3Rpb25zKSl7DQogICAgbWFjaDIgPC0gZ2VuZXJhdGVNYWNoaW5lc19NaXgodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0gdHJhaW5fcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IHNjYWxlX2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfbWFjaGluZSA9IHNjYWxlX21hY2hpbmUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IG1hY2hpbmVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gc3BsaXRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY01hY2hpbmVQYXJhbSkNCiAgfWVsc2V7DQogICAgbWFjaDIgPC0gbGlzdChmaXR0ZWRfcmVtYWluID0gdHJhaW5fcHJlZGljdGlvbnMsDQogICAgICAgICAgICAgICAgICBtb2RlbHMgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgaWQyID0gcmVwKFRSVUUsIG5yb3codHJhaW5faW5wdXQpKSwNCiAgICAgICAgICAgICAgICAgIHRyYWluX2RhdGEgPSBsaXN0KHRyYWluX2lucHV0ID0gdHJhaW5faW5wdXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3RfcmVtYWluX29yZyA9IHRyYWluX3ByZWRpY3Rpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX21hY2hpbmUgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X21hY2hpbmUgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX2lucHV0ID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pbnB1dCA9IE5VTEwpKQ0KICAgIGlmKHNjYWxlX21hY2hpbmUpew0KICAgICAgbWluXyA8LSBtYXBfZGJsKHRyYWluX3ByZWRpY3Rpb25zLCAuZiA9IG1pbikNCiAgICAgIG1heF8gPC0gbWFwX2RibCh0cmFpbl9wcmVkaWN0aW9ucywgLmYgPSBtYXgpDQogICAgICBtYWNoMiR0cmFpbl9kYXRhJG1pbl9tYWNoaW5lID0gbWluXw0KICAgICAgbWFjaDIkdHJhaW5fZGF0YSRtYXhfYW1jaGluZSA9IG1heF8NCiAgICAgIG1hY2gyJGZpdHRlZF9yZW1haW4gPC0gc2NhbGUodHJhaW5fcHJlZGljdGlvbnMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXIgPSBtaW5fLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGUgPSBtYXhfIC0gbWluXykNCiAgICB9DQogICAgaWYoc2NhbGVfaW5wdXQpew0KICAgICAgbWluXyA8LSBtYXBfZGJsKHRyYWluX2lucHV0LCAuZiA9IG1pbikNCiAgICAgIG1heF8gPC0gbWFwX2RibCh0cmFpbl9pbnB1dCwgLmYgPSBtYXgpDQogICAgICBtYWNoMiR0cmFpbl9kYXRhJG1pbl9pbnB1dCA9IG1pbl8NCiAgICAgIG1hY2gyJHRyYWluX2RhdGEkbWF4X2lucHV0ID0gbWF4Xw0KICAgICAgbWFjaDIkdHJhaW5fZGF0YSR0cmFpbl9pbnB1dCA8LSBzY2FsZSh0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IG1pbl8sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZSA9IG1heF8gLSBtaW5fKQ0KICAgIH0NCiAgfQ0KICAjIGRpc3RhbmNlIG1hdHJpeCB0byBjb21wdXRlIGxvc3MgZnVuY3Rpb24NCiAgaWZfZXVjbGlkIDwtIEZBTFNFDQogIGlkX2V1Y2xpZCA8LSBOVUxMDQogIG5fa2VyIDwtIGxlbmd0aChrZXJuZWxzKQ0KICBkaXN0X2FsbCA8LSBsaXN0KCkNCiAgaWRfc2h1ZiA8LSBOVUxMDQogIGZvciAoa18gaW4gMTpuX2tlcil7DQogICAga2VyIDwtIGtlcm5lbF9yZWFsW2tfXQ0KICAgIGlmKGtlciA9PSAibmFpdmUiKXsNCiAgICAgIGRpc3RfYWxsW1sibmFpdmUiXV0gPC0gZGlzdF9tYXRyaXhfTWl4KGJhc2ljTWFjaGluZXMgPSBtYWNoMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jdiA9IG5fY3YsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9ICJuYWl2ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmKQ0KICAgIH0gZWxzZXsNCiAgICAgIGlmKGtlciA9PSAidHJpYW5ndWxhciIpew0KICAgICAgICBkaXN0X2FsbFtbInRyaWFuZ3VsYXIiXV0gPC0gZGlzdF9tYXRyaXhfTWl4KGJhc2ljTWFjaGluZXMgPSBtYWNoMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSBuX2N2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVsID0gInRyaWFuZ3VsYXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWRfc2h1ZmZsZSA9IGlkX3NodWYpDQogICAgICB9IGVsc2V7DQogICAgICAgIGlmKGlmX2V1Y2xpZCl7DQogICAgICAgICAgZGlzdF9hbGxbW2tlcl1dIDwtIGRpc3RfYWxsW1tpZF9ldWNsaWRdXQ0KICAgICAgICB9IGVsc2V7DQogICAgICAgICAgZGlzdF9hbGxbW2tlcl1dIDwtIGRpc3RfbWF0cml4X01peChiYXNpY01hY2hpbmVzID0gbWFjaDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSBuX2N2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWwgPSBrZXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmKQ0KICAgICAgICAgIGlkX2V1Y2xpZCA8LSBrZXINCiAgICAgICAgICBpZl9ldWNsaWQgPC0gVFJVRQ0KICAgICAgICB9DQogICAgICB9DQogICAgfQ0KICAgIGlkX3NodWYgPC0gZGlzdF9hbGxbWzFdXSRpZF9zaHVmZmxlDQogIH0NCiAgIyBLZXJuZWwgZnVuY3Rpb25zIA0KICAjID09PT09PT09PT09PT09PT0NCiAgIyBHYXVzc2lhbg0KICBnYXVzc2lhbl9rZXJuZWwgPC0gZnVuY3Rpb24oLmVwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5pbnZfc2lnbWEgPSBpbnZfc2lnbWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuYWxwaGEgPSBhbHApew0KICBrZXJuX2Z1biA8LSBmdW5jdGlvbih4LCBpZCwgRDEsIEQyKXsNCiAgICB0ZW0wIDwtIGFzLm1hdHJpeChleHAoLSAoeFsxXSpEMSt4WzJdKkQyKV4oLmFscGhhLzIpKi5pbnZfc2lnbWFeLmFscGhhKSkNCiAgICB5X2hhdCA8LSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlICE9IGlkXSAlKiUgdGVtMC9jb2xTdW1zKHRlbTApDQogICAgcmV0dXJuKHN1bSgoeV9oYXQgLSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlID09IGlkXSleMikpDQogIH0NCiAgdGVtcCA8LSBtYXAoLnggPSAxOi5kaXN0X21hdHJpeCRuX2N2LCANCiAgICAgICAgICAgICAgLmYgPSB+IGtlcm5fZnVuKHggPSAuZXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQxID0gLmRpc3RfbWF0cml4JGRpc3RfaW5wdXRbWy54XV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDIgPSAuZGlzdF9tYXRyaXgkZGlzdF9tYWNoaW5lW1sueF1dKSkNCiAgcmV0dXJuKFJlZHVjZSgiKyIsIHRlbXApKQ0KfQ0KDQojIEVwYW5lY2huaWtvdg0KICBlcGFuZWNobmlrb3Zfa2VybmVsIDwtIGZ1bmN0aW9uKC5lcCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMil7DQogIGtlcm5fZnVuIDwtIGZ1bmN0aW9uKHgsIGlkLCBEMSwgRDIpew0KICAgIHRlbTAgPC0gYXMubWF0cml4KDEtICh4WzFdKkQxK3hbMl0qRCkpDQogICAgdGVtMFt0ZW0wIDwgMF0gPSAwDQogICAgeV9oYXQgPC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSAhPSBpZF0gJSolIHRlbTAvY29sU3Vtcyh0ZW0wKQ0KICAgIHJldHVybihzdW0oKHlfaGF0IC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSA9PSBpZF0pXjIpKQ0KICB9DQogIHRlbXAgPC0gbWFwKC54ID0gMTouZGlzdF9tYXRyaXgkbl9jdiwgDQogICAgICAgICAgICAgIC5mID0gfiBrZXJuX2Z1bih4ID0gLmVwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gLngsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMSA9IC5kaXN0X21hdHJpeCRkaXN0X2lucHV0W1sueF1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQyID0gLmRpc3RfbWF0cml4JGRpc3RfbWFjaGluZVtbLnhdXSkpDQogIHJldHVybihSZWR1Y2UoIisiLCB0ZW1wKSkNCiAgfQ0KDQojIEJpd2VpZ2h0DQogIGJpd2VpZ2h0X2tlcm5lbCA8LSBmdW5jdGlvbiguZXAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyKXsNCiAga2Vybl9mdW4gPC0gZnVuY3Rpb24oeCwgaWQsIEQxLCBEMil7DQogICAgdGVtMCA8LSBhcy5tYXRyaXgoMS0gKHhbMV0qRDEreFsyXSpEMikpDQogICAgdGVtMFt0ZW0wIDwgMF0gPSAwDQogICAgeV9oYXQgPC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSAhPSBpZF0gJSolIHRlbTBeMi9jb2xTdW1zKHRlbTBeMikNCiAgICB5X2hhdFtpcy5uYSh5X2hhdCldIDwtIDANCiAgICByZXR1cm4oc3VtKCh5X2hhdCAtIC50cmFpbl9yZXNwb25zZTJbLmRpc3RfbWF0cml4JGlkX3NodWZmbGUgPT0gaWRdKV4yKSkNCiAgfQ0KICB0ZW1wIDwtIG1hcCgueCA9IDE6LmRpc3RfbWF0cml4JG5fY3YsIA0KICAgICAgICAgICAgICAuZiA9IH4ga2Vybl9mdW4oeCA9IC5lcCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZCA9IC54LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDEgPSAuZGlzdF9tYXRyaXgkZGlzdF9pbnB1dFtbLnhdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMiA9IC5kaXN0X21hdHJpeCRkaXN0X21hY2hpbmVbWy54XV0pKQ0KICByZXR1cm4oUmVkdWNlKCIrIiwgdGVtcCkpDQogIH0NCg0KIyBUcml3ZWlnaHQNCiAgdHJpd2VpZ2h0X2tlcm5lbCA8LSBmdW5jdGlvbiguZXAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIpew0KICBrZXJuX2Z1biA8LSBmdW5jdGlvbih4LCBpZCwgRDEsIEQyKXsNCiAgICB0ZW0wIDwtIGFzLm1hdHJpeCgxLSAoeFsxXSpEMSt4WzJdKkQyKSkNCiAgICB0ZW0wW3RlbTAgPCAwXSA9IDANCiAgICB5X2hhdCA8LSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlICE9IGlkXSAlKiUgdGVtMF4zL2NvbFN1bXModGVtMF4zKQ0KICAgIHlfaGF0W2lzLm5hKHlfaGF0KV0gPC0gMA0KICAgIHJldHVybihzdW0oKHlfaGF0IC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSA9PSBpZF0pXjIpKQ0KICB9DQogIHRlbXAgPC0gbWFwKC54ID0gMTouZGlzdF9tYXRyaXgkbl9jdiwgDQogICAgICAgICAgICAgIC5mID0gfiBrZXJuX2Z1bih4ID0gLmVwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gLngsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMSA9IC5kaXN0X21hdHJpeCRkaXN0X2lucHV0W1sueF1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQyID0gLmRpc3RfbWF0cml4JGRpc3RfbWFjaGluZVtbLnhdXSkpDQogIHJldHVybihSZWR1Y2UoIisiLCB0ZW1wKSkNCiAgfQ0KDQojIFRyaWFuZ3VsYXINCiAgdHJpYW5ndWxhcl9rZXJuZWwgPC0gZnVuY3Rpb24oLmVwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIpew0KICBrZXJuX2Z1biA8LSBmdW5jdGlvbih4LCBpZCwgRDEsIEQyKXsNCiAgICB0ZW0wIDwtIGFzLm1hdHJpeCgxLSAoeFsxXSpEMSt4WzJdKkQyKSkNCiAgICB0ZW0wW3RlbTAgPCAwXSA8LSAwDQogICAgeV9oYXQgPC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSAhPSBpZF0gJSolIHRlbTAvY29sU3Vtcyh0ZW0wKQ0KICAgIHlfaGF0W2lzLm5hKHlfaGF0KV0gPSAwDQogICAgcmV0dXJuKHN1bSgoeV9oYXQgLSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlID09IGlkXSleMikpDQogIH0NCiAgdGVtcCA8LSBtYXAoLnggPSAxOi5kaXN0X21hdHJpeCRuX2N2LCANCiAgICAgICAgICAgICAgLmYgPSB+IGtlcm5fZnVuKHggPSAuZXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQxID0gLmRpc3RfbWF0cml4JGRpc3RfaW5wdXRbWy54XV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDIgPSAuZGlzdF9tYXRyaXgkZGlzdF9tYWNoaW5lW1sueF1dKSkNCiAgcmV0dXJuKFJlZHVjZSgiKyIsIHRlbXApKQ0KICB9DQoNCiAgIyBOYWl2ZQ0KICBuYWl2ZV9rZXJuZWwgPC0gZnVuY3Rpb24oLmVwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIpew0KICAgIGtlcm5fZnVuIDwtIGZ1bmN0aW9uKHgsIGlkLCBEMSwgRDIpew0KICAgICAgdGVtMCA8LSAoYXMubWF0cml4KCh4WzFdKkQxK3hbMl0qRDIpKSA8IDEpDQogICAgICB5X2hhdCA8LSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlICE9IGlkXSAlKiUgdGVtMC9jb2xTdW1zKHRlbTApDQogICAgICB5X2hhdFtpcy5uYSh5X2hhdCldID0gMA0KICAgICAgcmV0dXJuKHN1bSgoeV9oYXQgLSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlID09IGlkXSleMikpDQogICAgfQ0KICAgIHRlbXAgPC0gbWFwKC54ID0gMTouZGlzdF9tYXRyaXgkbl9jdiwgDQogICAgICAgICAgICAgICAgLmYgPSB+IGtlcm5fZnVuKHggPSAuZXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZCA9IC54LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMSA9IC5kaXN0X21hdHJpeCRkaXN0X2lucHV0W1sueF1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDIgPSAuZGlzdF9tYXRyaXgkZGlzdF9tYWNoaW5lW1sueF1dKSkNCiAgICByZXR1cm4oUmVkdWNlKCIrIiwgdGVtcCkpDQogIH0NCiAgDQogICMgbGlzdCBvZiBrZXJuZWwgZnVuY3Rpb25zDQogIGxpc3RfZnVucyA8LSBsaXN0KGdhdXNzaWFuID0gZ2F1c3NpYW5fa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICBlcGFuZWNobmlrb3YgPSBlcGFuZWNobmlrb3Zfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICBiaXdlaWdodCA9IGJpd2VpZ2h0X2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgdHJpd2VpZ2h0ID0gdHJpd2VpZ2h0X2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgdHJpYW5ndWxhciA9IHRyaWFuZ3VsYXJfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICBuYWl2ZSA9IG5haXZlX2tlcm5lbCkNCiAgDQogICMgZXJyb3IgZm9yIGFsbCBrZXJuZWwgZnVuY3Rpb25zDQogIGVycm9yX2Z1bmMgPC0ga2VybmVsX3JlYWwgJT4lDQogICAgbWFwKC5mID0gfiAoXCh4KSBsaXN0X2Z1bnNbWy54XV0oLmVwID0geCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXggPSBkaXN0X2FsbFtbLnhdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyID0gdHJhaW5fcmVzcG9uc2VbbWFjaDIkaWQyXSkvbl9jdikpDQogIG5hbWVzKGVycm9yX2Z1bmMpIDwtIGtlcm5lbHMNCiAgIyBsaXN0IG9mIHByYW1ldGVyIHNldHVwDQogIGxpc3RfcGFyYW0gPC0gbGlzdChncmFkID0gc2V0R3JhZFBhcmFtLA0KICAgICAgICAgICAgICAgICAgICAgR0QgPSBzZXRHcmFkUGFyYW0sDQogICAgICAgICAgICAgICAgICAgICBncmlkID0gc2V0R3JpZFBhcmFtKQ0KICAjIGxpc3Qgb2Ygb3B0aW1pemVyDQogIGxpc3Rfb3B0aW1pemVyIDwtIGxpc3QoZ3JhZCA9IGdyYWRPcHRpbWl6ZXJfTWl4LA0KICAgICAgICAgICAgICAgICAgICAgICAgIEdEID0gZ3JhZE9wdGltaXplcl9NaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgZ3JpZCA9IGdyaWRPcHRpbWl6ZXJfTWl4KQ0KICBvcHRNZXRob2RzIDwtIG9wdGltaXplTWV0aG9kDQogIGlmKGxlbmd0aChrZXJuZWxzKSAhPSBsZW5ndGgob3B0TWV0aG9kcykpew0KICAgIHdhcm5pbmcoIioga2VybmVscyBhbmQgb3B0aW1pemF0aW9uIG1ldGhvZHMgZGlmZmVyIGluIHNpZGVzISBHcmlkIHNlYXJjaCB3aWxsIGJlIHVzZWQhIikNCiAgICBvcHRNZXRob2RzID0gcmVwKCJncmlkIiwgbGVuZ3RoKGtlcm5lbHMpKQ0KICB9DQoNCiAgIyBPcHRpbWl6YXRpb24NCiAgcGFyYW1ldGVycyA8LSBtYXAyKC54ID0ga2VybmVscywNCiAgICAgICAgICAgICAgICAgICAgIC55ID0gb3B0TWV0aG9kcywgDQogICAgICAgICAgICAgICAgICAgICAuZiA9IH4gbGlzdF9vcHRpbWl6ZXJbWy55XV0ob2JqX2Z1biA9IGVycm9yX2Z1bmNbWy54XV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0UGFyYW1ldGVyID0gbGlzdF9wYXJhbVtbLnldXSkpDQogIG5hbWVzKHBhcmFtZXRlcnMpIDwtIHBhc3RlMChrZXJuZWxfcmVhbCwgIl8iLCBvcHRNZXRob2RzKQ0KICByZXR1cm4obGlzdChvcHRfcGFyYW1ldGVycyA9IHBhcmFtZXRlcnMsDQogICAgICAgICAgICAgIGFkZF9wYXJhbWV0ZXJzID0gbGlzdChpbnZfc2lnbWEgPSBpbnZfc2lnbWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHAgPSBhbHAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcHRfbWV0aG9kcyA9IG9wdGltaXplTWV0aG9kKSwNCiAgICAgICAgICAgICAgYmFzaWNfbWFjaGluZXMgPSBtYWNoMikpDQp9DQpgYGANCg0KLS0tDQoNCj4gKipFeGFtcGxlLjUqKjogV2UgYXBwcm94aW1hdGUgdGhlIHNtb290aGluZyBwYXJhbWV0ZXIgb2YgYEJvc3RvbmAgZGF0YS4NCg0KLS0tDQoNCmBgYHtyfQ0KZGYgPC0gTUFTUzo6Qm9zdG9uDQp0cmFpbiA8LSBsb2dpY2FsKG5yb3coZGYpKQ0KdHJhaW5bc2FtcGxlKGxlbmd0aCh0cmFpbiksIGZsb29yKDAuNzUqbnJvdyhkZikpKV0gPC0gVFJVRQ0KDQpwYXJhbSA8LSBmaXRfcGFyYW1ldGVyX01peCh0cmFpbl9pbnB1dCA9IGRmW3RyYWluLCAxOjEzXSwNCiAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSBkZlt0cmFpbiwgMTRdLA0KICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IGMoImtubiIsICJyZiIsICJ4Z2IiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gLjUwLA0KICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWxzID0gYygiZ2F1c3NpYW4iLCAiYml3ZWlnaHQiLCAidHJpYW5ndWxhciIpLA0KICAgICAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZU1ldGhvZCA9IGMoImdyYWQiLCAiZ3JpZCIsICJncmlkIiksDQogICAgICAgICAgICAgICAgICAgICAgIHNldEJhc2ljTWFjaGluZVBhcmFtID0gc2V0QmFzaWNQYXJhbWV0ZXJfTWl4KGsgPSAyOjYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSAxOjUqMTAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHNfeGdiID0gMTo1KjEwMCksDQogICAgICAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbSA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KHJhdGUgPSAibGluZWFyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9zdGVwID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZl9hdXRvID0gYygwLjMsIDAuMykpLA0KICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1ldGVyX01peChtaW5fYWxwaGEgPSAwLjAwMDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2FscGhhID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fYmV0YSA9IDAuMDAwMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYmV0YSA9IDMpKQ0KYGBgDQoNCmBgYHtyfQ0KcGFyYW0kb3B0X3BhcmFtZXRlcnMgJT4lDQogIG1hcF9kZmMoLmYgPSB+IC54JG9wdF9wYXJhbSkgJT4lDQogIHByaW50DQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij48dT5QcmVkaWN0aW9uPC91Pjwvc3Bhbj4NCj09PQ0KDQpUaGUgc21vb3RoaW5nIHBhcmFtZXRlciBvYnRhaW5lZCBmcm9tIHRoZSBwcmV2aW91cyBzZWN0aW9uIGNhbiBiZSB1c2VkIHRvIG1ha2UgdGhlIGZpbmFsIHByZWRpY3Rpb25zLiANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+S2VybmVsIGZ1bmN0aW9uczwvdT48L3NwYW4+DQotLS0NCg0KU2V2ZXJhbCB0eXBlcyBvZiBrZXJuZWwgZnVuY3Rpb25zIHVzZWQgZm9yIHRoZSBhZ2dyZWdhdGlvbiBhcmUgZGVmaW5lZCBpbiB0aGlzIHNlY3Rpb24uDQoNCi0gKipBcmd1bWVudCoqOg0KICAgIA0KICAgIC0gYHRoZXRhYCA6IGEgMkQtdmVjdG9yIG9mIHRoZSBwYXJhbWV0ZXIgJChcYWxwaGEsIFxiZXRhKSQgdXNlZC4NCiAgICAtIGAueTJgIDogdGhlIHZlY3RvciBvZiByZXNwb25zZSB2YXJpYWJsZSBvZiB0aGUgc2Vjb25kIHBhcnQgJFxtYXRoY2Fse0R9X3tcZWxsfSQgb2YgdGhlIHRyYWluaW5nIGRhdGEsIHdoaWNoIGlzIHVzZWQgZm9yIHRoZSBhZ2dyZWdhdGlvbi4NCiAgICAtIGAuZGlzdGFuY2VgIDogdGhlIGRpc3RhbmNlIG1hdHJpeCBvYmplY3Qgb2J0YWluZWQgZnJvbSBgZGlzdF9tYXRyaXhgIGZ1bmN0aW9uLg0KICAgIC0gYC5rZXJuYCA6IHRoZSBzdHJpbmcgc3BlY2lmeWluZyB0aGUga2VybmVsIGZ1bmN0aW9uLiBCeSBkZWZhdWx0LCBgLmtlcm4gPSAiZ2F1c3NpYW4iYC4NCiAgICAtIGAuaW52X3NpZywgLmFscGAgOiB0aGUgcGFyYW1ldGVycyBvZiBleHBvbmVudGlhbCBrZXJuZWwgZnVuY3Rpb24uDQogICAgLSBgLm1ldGhgIDogdGhlIHN0cmluZyBvZiBvcHRpbWl6YXRpb24gbWV0aG9kcyB0byBiZSBsaW5rZWQgdG8gdGhlIG5hbWUgb2YgdGhlIGtlcm5lbCBmdW5jdGlvbnMgaWYgYW55IHBlcnRpY3VsYXIga2VybmVscyBhcmUgdXNlZCBtb3JlIHRoYW4gb25jZSAobWF5YmUgd2l0aCBkaWZmZXJlbnQgb3B0aW1penRhaW9uIG1ldGhvZCBzdWNoIGFzICJnYXVzc2lhbiIga2VybmVsLCBjYW4gYmUgdXNlZCB3aXRoIGJvdGggImdyYWQiIGFuZCAiZ3JpZCIgb3B0aW1pemF0aW9uIG1ldGhvZHMpLg0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgdGhlIHByZWRpY3Rpb25zIG9mIHRoZSBhZ2dyZWdhdGlvbiBtZXRob2QgZXZhbHVhdGVkIHdpdGggdGhlIGdpdmVuIHBhcmFtZXRlci4NCg0KDQpgYGB7cn0NCmtlcm5lbF9wcmVkX01peCA8LSBmdW5jdGlvbih0aGV0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgIC55MiwgDQogICAgICAgICAgICAgICAgICAgICAgICAuZGlzdDEsDQogICAgICAgICAgICAgICAgICAgICAgICAuZGlzdDIsDQogICAgICAgICAgICAgICAgICAgICAgICAua2VybiA9ICJnYXVzc2lhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAuaW52X3NpZyA9IHNxcnQoLjUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIC5hbHAgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgLm1ldGggPSBOQSl7DQogIGRpc3REIDwtIGFzLm1hdHJpeCh0aGV0YVsxXSouZGlzdDErdGhldGFbMl0qLmRpc3QxKQ0KICAjIEtlcm5lbCBmdW5jdGlvbnMNCiAgIyA9PT09PT09PT09PT09PT09DQogIGdhdXNzaWFuX2tlcm5lbCA8LSBmdW5jdGlvbihELA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmludl9zaWdtYSA9IC5pbnZfc2lnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmFscGhhID0gLmFscCl7DQogICAgdGVtMCA8LSBleHAoLSBEXiguYWxwaGEvMikqLmludl9zaWdeLmFscGhhKQ0KICAgIHlfaGF0IDwtIC55MiAlKiUgdGVtMC9jb2xTdW1zKHRlbTApDQogICAgcmV0dXJuKHQoeV9oYXQpKQ0KICB9DQoNCiAgIyBFcGFuZWNobmlrb3YNCiAgZXBhbmVjaG5pa292X2tlcm5lbCA8LSBmdW5jdGlvbihEKXsNCiAgICB0ZW0wIDwtIDEtIEQNCiAgICB0ZW0wW3RlbTAgPCAwXSA9IDANCiAgICB5X2hhdCA8LSAueTIgJSolIHRlbTAvY29sU3Vtcyh0ZW0wKQ0KICAgIHJldHVybih0KHlfaGF0KSkNCiAgfQ0KICAjIEJpd2VpZ2h0DQogIGJpd2VpZ2h0X2tlcm5lbCA8LSBmdW5jdGlvbihEKXsNCiAgICB0ZW0wIDwtIDEtIEQNCiAgICB0ZW0wW3RlbTAgPCAwXSA9IDANCiAgICB5X2hhdCA8LSAueTIgJSolIHRlbTBeMi9jb2xTdW1zKHRlbTBeMikNCiAgICB5X2hhdFtpcy5uYSh5X2hhdCldIDwtIDANCiAgICByZXR1cm4odCh5X2hhdCkpDQogIH0NCg0KICAjIFRyaXdlaWdodA0KICB0cml3ZWlnaHRfa2VybmVsIDwtIGZ1bmN0aW9uKEQpew0KICAgIHRlbTAgPC0gMS0gRA0KICAgIHRlbTBbdGVtMCA8IDBdID0gMA0KICAgIHlfaGF0IDwtIC55MiAlKiUgdGVtMF4zL2NvbFN1bXModGVtMF4zKQ0KICAgIHlfaGF0W2lzLm5hKHlfaGF0KV0gPC0gMA0KICAgIHJldHVybih0KHlfaGF0KSkNCiAgfQ0KDQogICMgVHJpYW5ndWxhcg0KICB0cmlhbmd1bGFyX2tlcm5lbCA8LSBmdW5jdGlvbihEKXsNCiAgICB0ZW0wIDwtIDEtIEQNCiAgICB0ZW0wW3RlbTAgPCAwXSA8LSAwDQogICAgeV9oYXQgPC0gLnkyICUqJSB0ZW0wL2NvbFN1bXModGVtMCkNCiAgICB5X2hhdFtpcy5uYSh5X2hhdCldID0gMA0KICAgIHJldHVybih0KHlfaGF0KSkNCiB9DQogICMgTmFpdmUNCiAgbmFpdmVfa2VybmVsIDwtIGZ1bmN0aW9uKEQpew0KICAgICAgdGVtMCA8LSAoRCA8IDEpDQogICAgICB5X2hhdCA8LSAueTIgJSolIHRlbTAvY29sU3Vtcyh0ZW0wKQ0KICAgICAgeV9oYXRbaXMubmEoeV9oYXQpXSA9IDANCiAgICAgIHJldHVybih0KHlfaGF0KSkNCiAgfQ0KICAjIFByZWRpY3Rpb24NCiAga2VybmVsX2xpc3QgPC0gbGlzdChnYXVzc2lhbiA9IGdhdXNzaWFuX2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgICBlcGFuZWNobmlrb3YgPSBlcGFuZWNobmlrb3Zfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICAgIGJpd2VpZ2h0ID0gYml3ZWlnaHRfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICAgIHRyaXdlaWdodCA9IHRyaXdlaWdodF9rZXJuZWwsDQogICAgICAgICAgICAgICAgICAgICAgdHJpYW5ndWxhciA9IHRyaWFuZ3VsYXJfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICAgIG5haXZlID0gbmFpdmVfa2VybmVsKQ0KICByZXMgPC0gdGliYmxlKGFzLnZlY3RvcihrZXJuZWxfbGlzdFtbLmtlcm5dXShEID0gZGlzdEQpKSkNCiAgbmFtZXMocmVzKSA8LSBpZmVsc2UoaXMubmEoLm1ldGgpLCANCiAgICAgICAgICAgICAgICAgICAgICAgLmtlcm4sIA0KICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoLmtlcm4sICdfJywgLm1ldGgpKQ0KICByZXR1cm4ocmVzKQ0KfQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb25zPC91Pjwvc3Bhbj46IGBwcmVkaWN0X01peGANCi0tLQ0KDQotICoqQXJndW1lbnQqKjoNCiAgICANCiAgICAtIGBmaXR0ZWRfbW9kZWxzYCA6IHRoZSBvYmplY3Qgb2J0YWluZWQgZnJvbSBgZml0X3BhcmFtZXRlcl9NaXhgIGZ1bmN0aW9uLg0KICAgIC0gYG5ld19kYXRhYCA6IHRoZSB0ZXN0aW5nIGRhdGEgdG8gYmUgcHJlZGljdGVkLg0KICAgIC0gYHRlc3RfcmVzcG9uc2VgIDogdGhlIHRlc3RpbmcgcmVzcG9uc2UgdmFyaWFibGUsIGl0IGlzIG9wdGlvbmFsLiBJZiBpdCBpcyBnaXZlbiwgdGhlIG1lYW4gc3F1YXJlIGVycm9yIG9uIHRoZSB0ZXN0aW5nIGRhdGEgaXMgYWxzbyBjb21wdXRlZC4gQnkgZGVmYXVsdCwgYHRlc3RfcmVzcG9uc2UgPSBOVUxMYC4NCiAgICANCi0gKipWYWx1ZSoqOg0KDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKmxpc3QqIG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICANCiAgICAtIGBmaXR0ZWRfYWdncmVnYXRlYCA6IHRoZSBwcmVkaWN0aW9ucyBieSB0aGUgYWdncmVnYXRpb24gbWV0aG9kcy4NCiAgICAtIGBmaXR0ZWRfbWFjaGluZWAgOiB0aGUgcHJlZGljdGlvbnMgZ2l2ZW4gYnkgYWxsIHRoZSBiYXNpYyBtYWNoaW5lcy4NCiAgICAtIGBtc2VgIDogdGhlIG1lYW4gc3F1YXJlIGVycm9yIGNvbXB1dGVkIG9ubHkgaWYgdGhlIGB0ZXN0X3JlcG9uc2VgIGFyZ3VtZW50IGlzIG5vdGUgYE5VTExgLg0KDQoNCmBgYHtyfQ0KIyBQcmVkaWN0aW9uDQpwcmVkaWN0X01peCA8LSBmdW5jdGlvbihmaXR0ZWRfbW9kZWxzLA0KICAgICAgICAgICAgICAgICAgICAgICAgbmV3X2RhdGEsDQogICAgICAgICAgICAgICAgICAgICAgICB0ZXN0X3Jlc3BvbnNlID0gTlVMTCl7DQogIG9wdF9wYXJhbSA8LSBmaXR0ZWRfbW9kZWxzJG9wdF9wYXJhbWV0ZXJzDQogIGFkZF9wYXJhbSA8LSBmaXR0ZWRfbW9kZWxzJGFkZF9wYXJhbWV0ZXJzDQogIGJhc2ljX21hY2ggPC0gZml0dGVkX21vZGVscyRiYXNpY19tYWNoaW5lcw0KICBrZXJuMCA8LSBuYW1lcyhvcHRfcGFyYW0pDQogIGtlcm5lbDAgPC0gc3RyaW5ncjo6c3RyX3NwbGl0KGtlcm4wLCAiXyIpICU+JQ0KICAgIGltYXBfZGZjKC5mID0gfiB0aWJibGUoInsueX0iIDo9IC54KSkgDQogIGtlcm5zIDwtIGtlcm5lbDBbMSxdICU+JQ0KICAgIGFzLmNoYXJhY3Rlcg0KICBvcHRfbWV0aHMgPC0ga2VybmVsMFsyLF0gJT4lDQogICAgYXMuY2hhcmFjdGVyDQogIG5ld19kYXRhXyA8LSBuZXdfZGF0YQ0KICBtYXRfaW5wdXQgPC0gYXMubWF0cml4KGJhc2ljX21hY2gkdHJhaW5fZGF0YSR0cmFpbl9pbnB1dCkNCiAgaWYoIWlzLm51bGwoYmFzaWNfbWFjaCR0cmFpbl9kYXRhJG1pbl9pbnB1dCkpew0KICAgIG5ld19kYXRhXyA8LSBzY2FsZShuZXdfZGF0YSwgDQogICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IGJhc2ljX21hY2gkdHJhaW5fZGF0YSRtaW5faW5wdXQsIA0KICAgICAgICAgICAgICAgICAgICAgICBzY2FsZSA9IGJhc2ljX21hY2gkdHJhaW5fZGF0YSRtYXhfaW5wdXQgLSBiYXNpY19tYWNoJHRyYWluX2RhdGEkbWluX2lucHV0KQ0KICB9DQogIGlmKGlzLm1hdHJpeChuZXdfZGF0YV8pKXsNCiAgICBtYXRfdGVzdCA8LSBuZXdfZGF0YV8NCiAgICBkZl90ZXN0IDwtIGFzX3RpYmJsZShuZXdfZGF0YV8pDQogIH0gZWxzZSB7DQogICAgbWF0X3Rlc3QgPC0gYXMubWF0cml4KG5ld19kYXRhXykNCiAgICBkZl90ZXN0IDwtIG5ld19kYXRhXw0KICB9DQogIGlmKGlzLm51bGwoYmFzaWNfbWFjaCRtb2RlbHMpKXsNCiAgICBwcmVkX3Rlc3RfYWxsIDwtIG5ld19kYXRhXw0KICAgIHByZWRfdGVzdDAgPC0gbmV3X2RhdGENCiAgfWVsc2V7DQogICAgIyBQcmVkaWN0aW9uIHRlc3QgYnkgYmFzaWMgbWFjaGluZXMNCiAgICBidWlsdF9tb2RlbHMgPC0gYmFzaWNfbWFjaCRtb2RlbHMNCiAgICBwcmVkX3Rlc3QgPC0gZnVuY3Rpb24obWV0aCl7DQogICAgICBpZihtZXRoID09ICJrbm4iKXsNCiAgICAgICAgcHJlIDwtIDE6bGVuZ3RoKGJ1aWx0X21vZGVsc1tbbWV0aF1dKSAlPiUNCiAgICAgICAgICBtYXBfZGZjKC5mID0gKFwoaykgdGliYmxlKCd7e2t9fScgOj0gRk5OOjprbm4ucmVnKHRyYWluID0gbWF0X2lucHV0WyFiYXNpY19tYWNoJGlkMixdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSBtYXRfdGVzdCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gYmFzaWNfbWFjaCR0cmFpbl9kYXRhJHRyYWluX3Jlc3BvbnNlWyFiYXNpY19tYWNoJGlkMl0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gYnVpbHRfbW9kZWxzW1ttZXRoXV1bW2tdXSkkcHJlZCkpKQ0KICAgICAgfQ0KICAgICAgaWYobWV0aCAlaW4lIGMoInRyZWUiLCAicmYiKSl7DQogICAgICAgIHByZSA8LSAxOmxlbmd0aChidWlsdF9tb2RlbHNbW21ldGhdXSkgJT4lDQogICAgICAgICAgbWFwX2RmYyguZiA9IChcKGspIHRpYmJsZSgne3trfX0nIDo9IGFzLnZlY3RvcihwcmVkaWN0KGJ1aWx0X21vZGVsc1tbbWV0aF1dW1trXV0sIGRmX3Rlc3QpKSkpKQ0KICAgICAgfQ0KICAgICAgaWYobWV0aCAlaW4lIGMoImxhc3NvIiwgInJpZGdlIikpew0KICAgICAgICBwcmUgPC0gMTpsZW5ndGgoYnVpbHRfbW9kZWxzW1ttZXRoXV0pICU+JQ0KICAgICAgICAgIG1hcF9kZmMoLmYgPSAoXChrKSB0aWJibGUoJ3t7a319JyA6PSBhcy52ZWN0b3IocHJlZGljdC5nbG1uZXQoYnVpbHRfbW9kZWxzW1ttZXRoXV1bW2tdXSwgbWF0X3Rlc3QpKSkpKQ0KICAgICAgfQ0KICAgICAgaWYobWV0aCA9PSAieGdiIil7DQogICAgICAgIHByZSA8LSAxOmxlbmd0aChidWlsdF9tb2RlbHNbW21ldGhdXSkgJT4lDQogICAgICAgICAgbWFwX2RmYyguZiA9IChcKGspIHRpYmJsZSgne3trfX0nIDo9IGFzLnZlY3RvcihwcmVkaWN0KGJ1aWx0X21vZGVsc1tbbWV0aF1dW1trXV0sIG1hdF90ZXN0KSkpKSkNCiAgICAgIH0NCiAgICAgIGNvbG5hbWVzKHByZSkgPC0gbmFtZXMoYnVpbHRfbW9kZWxzW1ttZXRoXV0pDQogICAgICByZXR1cm4ocHJlKQ0KICAgIH0NCiAgICBwcmVkX3Rlc3RfYWxsIDwtIG5hbWVzKGJ1aWx0X21vZGVscykgJT4lDQogICAgICBtYXBfZGZjKC5mID0gcHJlZF90ZXN0KQ0KICAgIHByZWRfdGVzdDAgPC0gcHJlZF90ZXN0X2FsbA0KICAgIGlmKCFpcy5udWxsKGJhc2ljX21hY2gkdHJhaW5fZGF0YSRtaW5fbWFjaGluZSkpew0KICAgICAgICBwcmVkX3Rlc3RfYWxsIDwtIHNjYWxlKHByZWRfdGVzdDAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IGJhc2ljX21hY2gkdHJhaW5fZGF0YSRtaW5fbWFjaGluZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZSA9IGJhc2ljX21hY2gkdHJhaW5fZGF0YSRtYXhfbWFjaGluZSAtIGJhc2ljX21hY2gkdHJhaW5fZGF0YSRtaW5fbWFjaGluZSkNCiAgICB9DQogIH0NCiAgDQogICMgUHJlZGljdGlvbiB0cmFpbjINCiAgcHJlZF90cmFpbl9hbGwgPC0gYmFzaWNfbWFjaCRmaXR0ZWRfcmVtYWluDQogIGNvbG5hbWVzKHByZWRfdGVzdF9hbGwpIDwtIGNvbG5hbWVzKHByZWRfdHJhaW5fYWxsKQ0KICBkX3RyYWluIDwtIGRpbShwcmVkX3RyYWluX2FsbCkNCiAgZF90ZXN0IDwtIGRpbShwcmVkX3Rlc3RfYWxsKQ0KICBkX3RyYWluX2lucHV0IDwtIGRpbShtYXRfaW5wdXRbYmFzaWNfbWFjaCRpZDIsXSkNCiAgZF90ZXN0X2lucHV0IDwtIGRpbShuZXdfZGF0YV8pDQogIHByZWRfdGVzdF9tYXQgPC0gYXMubWF0cml4KHByZWRfdGVzdF9hbGwpDQogIHByZWRfdHJhaW5fbWF0IDwtIGFzLm1hdHJpeChwcmVkX3RyYWluX2FsbCkNCiAgIyBEaXN0YW5jZSBtYXRyaXgNCiAgZGlzdF9tYXQgPC0gZnVuY3Rpb24oa2VybmVsID0gImdhdXNpYW4iKXsNCiAgICByZXNfMSA8LSByZXNfMiA8LSBOVUxMDQogICAgaWYoIShrZXJuZWwgJWluJSBjKCJuYWl2ZSIsICJ0cmlhbmd1bGFyIikpKXsNCiAgICAgIHJlc18xIDwtIDE6ZF90ZXN0X2lucHV0WzFdICU+JQ0KICAgICAgICBtYXBfZGZjKC5mID0gKFwoaWQpIHRpYmJsZSgne3tpZH19JyA6PSBhcy52ZWN0b3Iocm93U3VtcygobWF0X2lucHV0W2Jhc2ljX21hY2gkaWQyLF0gLSBtYXRyaXgocmVwKG5ld19kYXRhX1tpZCxdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkX3RyYWluX2lucHV0WzFdKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gZF90cmFpbl9pbnB1dFsyXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieXJvdyA9IFRSVUUpKV4yKSkpKSkNCiAgICAgIHJlc18yIDwtIDE6ZF90ZXN0WzFdICU+JQ0KICAgICAgICBtYXBfZGZjKC5mID0gKFwoaWQpIHRpYmJsZSgne3tpZH19JyA6PSBhcy52ZWN0b3Iocm93U3VtcygocHJlZF90cmFpbl9tYXQgLSBtYXRyaXgocmVwKHByZWRfdGVzdF9tYXRbaWQsXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZF90cmFpblsxXSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IGRfdHJhaW5bMl0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBUUlVFKSleMikpKSkpDQogICAgfQ0KICAgIGlmKGtlcm5lbCA9PSAidHJpYW5ndWxhciIpew0KICAgICAgcmVzXzEgPC0gMTpkX3Rlc3RfaW5wdXRbMV0gJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3Rvcihyb3dTdW1zKGFicyhtYXRfaW5wdXRbYmFzaWNfbWFjaCRpZDIsXSAtIG1hdHJpeChyZXAobmV3X2RhdGFfW2lkLF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRfdHJhaW5faW5wdXRbMV0pLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gZF90cmFpbl9pbnB1dFsyXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBUUlVFKSkpKSkpKQ0KICAgICAgcmVzXzIgPC0gMTpkX3Rlc3RbMV0gJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3Rvcihyb3dTdW1zKGFicyhwcmVkX3RyYWluX21hdCAtIG1hdHJpeChyZXAocHJlZF90ZXN0X21hdFtpZCxdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkX3RyYWluWzFdKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gZF90cmFpblsyXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieXJvdyA9IFRSVUUpKSkpKSkpDQogICAgfQ0KICAgIGlmKGtlcm5lbCA9PSAibmFpdmUiKXsNCiAgICAgIHJlc18xIDwtIDE6ZF90ZXN0X2lucHV0WzFdICU+JQ0KICAgICAgICBtYXBfZGZjKC5mID0gKFwoaWQpIHRpYmJsZSgne3tpZH19JyA6PSBhcy52ZWN0b3IoYXBwbHkoYWJzKG1hdF9pbnB1dFtiYXNpY19tYWNoJGlkMixdIC0gbWF0cml4KHJlcChuZXdfZGF0YV9baWQsXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZF90cmFpbl9pbnB1dFsxXSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IGRfdHJhaW5faW5wdXRbMl0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSkpLCAxLCBtYXgpKSkpKQ0KICAgICAgcmVzXzIgPC0gMTpkX3Rlc3RbMV0gJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3RvcihhcHBseShhYnMocHJlZF90cmFpbl9tYXQgLSBtYXRyaXgocmVwKHByZWRfdGVzdF9tYXRbaWQsXSwgZF90cmFpblsxXSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBkX3RyYWluWzJdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieXJvdyA9IFRSVUUpKSwgMSwgbWF4KSkpKSkNCiAgICB9DQogICAgcmV0dXJuKGxpc3QoZGlzdF9pbnB1dCA9IHJlc18xLA0KICAgICAgICAgICAgICAgIGRpc3RfbWFjaGluZSA9IHJlc18yKSkNCiAgfQ0KDQogIGRpc3RzIDwtIDE6bGVuZ3RoKGtlcm5zKSAlPiUNCiAgICAgIG1hcCguZiA9IH4gZGlzdF9tYXQoa2VybnNbLnhdKSkNCiAgdGFiX25hbSA8LSB0YWJsZShrZXJucykNCiAgbmFtIDwtIG5hbWVzKHRhYl9uYW1bdGFiX25hbSA+IDFdKQ0KICB2ZWMgPC0gcmVwKE5BLCBsZW5ndGgoa2VybnMpKQ0KICBmb3IoaWQgaW4gbmFtKXsNCiAgICBpZF8gPC0ga2VybnMgPT0gaWQNCiAgICBpZighaXMubnVsbChpZF8pKXsNCiAgICAgIHZlY1tpZF9dID0gYWRkX3BhcmFtJG9wdF9tZXRob2RzW2lkX10NCiAgICB9DQogIH0NCg0KICBwcmVkaWN0aW9uIDwtIDE6bGVuZ3RoKGtlcm5zKSAlPiUgDQogICAgbWFwX2RmYyguZiA9IH4ga2VybmVsX3ByZWRfTWl4KHRoZXRhID0gb3B0X3BhcmFtW1trZXJuMFsueF1dXSRvcHRfcGFyYW0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnkyID0gYmFzaWNfbWFjaCR0cmFpbl9kYXRhJHRyYWluX3Jlc3BvbnNlW2Jhc2ljX21hY2gkaWQyXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdDEgPSBkaXN0c1tbLnhdXSRkaXN0X2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5kaXN0MiA9IGRpc3RzW1sueF1dJGRpc3RfbWFjaGluZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAua2VybiA9IGtlcm5zWy54XSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmludl9zaWcgPSBhZGRfcGFyYW0kaW52X3NpZ21hLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuYWxwID0gYWRkX3BhcmFtJGFscCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAubWV0aCA9IHZlY1sueF0pKQ0KICBpZihpcy5udWxsKHRlc3RfcmVzcG9uc2UpKXsNCiAgICByZXR1cm4obGlzdChmaXR0ZWRfYWdncmVnYXRlID0gcHJlZGljdGlvbiwNCiAgICAgICAgICAgZml0dGVkX21hY2hpbmUgPSBwcmVkX3Rlc3QwKSkNCiAgfSBlbHNlew0KICAgIGVycm9yIDwtIGNiaW5kKHByZWRfdGVzdDAsIHByZWRpY3Rpb24pICU+JQ0KICAgICAgZHBseXI6Om11dGF0ZSh5X3Rlc3QgPSB0ZXN0X3Jlc3BvbnNlKSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiAoLiAtIHlfdGVzdCkpICU+JQ0KICAgICAgZHBseXI6OnNlbGVjdCgteV90ZXN0KSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiBtZWFuKC5eMikpDQogICAgcmV0dXJuKGxpc3QoZml0dGVkX2FnZ3JlZ2F0ZSA9IHByZWRpY3Rpb24sDQogICAgICAgICAgICAgICAgZml0dGVkX21hY2hpbmUgPSBwcmVkX3Rlc3QwLA0KICAgICAgICAgICAgICAgIG1zZSA9IGVycm9yKSkNCiAgfQ0KfQ0KYGBgDQoNCi0tLQ0KDQo+ICoqRXhhbXBsZS42KiogQWdncmVnYXRpb24gb24gYEJvc3RvbmAgZGF0YXNldC4NCg0KLS0tDQoNCmBgYHtyfQ0KcHJlZCA8LSBwcmVkaWN0X01peChwYXJhbSwNCiAgICAgICAgICAgIG5ld19kYXRhID0gZGZbIXRyYWluLCAtMTRdLA0KICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IGRmWyF0cmFpbiwgMTRdKQ0Kc3FydChwcmVkJG1zZSkNCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBgTWl4Q29icmFSZWdgIChkaXJlY3QgYWdncmVnYXRpb24pDQo9PT0NCg0KVGhpcyBmdW5jdGlvbiBwdXRzIHRvZ2V0aGVyIGFsbCB0aGUgZnVuY3Rpb25zIGFib3ZlIGFuZCBwcm92aWRlcyB0aGUgZGVzaXJlIHJlc3VsdCBvZiBNaXhDb2JyYSBhZ2dyZWdhdGlvbiBtZXRob2QuDQoNCi0gKipBcmd1bWVudCoqOiBhbGwgb2YgaXRzIGFyZ3VtZW50cyBhcmUgYWxyZWFkeSBkZXNjcmliZWQgaW4gdGhlIHByZXZpb3VzIHBhcnRzLg0KICAgIA0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybiBhICpsaXN0KiBvZiB0aGUgZm9sbG93aW5nIG9iamVjdHM6DQogICAgDQogICAgLSBgZml0dGVkX2FnZ3JlZ2F0ZWAgOiB0aGUgcHJlZGljdGVkIHZhbHVlcyBvZiB0aGUgYWdncmVnYXRpb24gbWV0aG9kLg0KICAgIC0gYGZpdHRlZF9tYWNoaW5lYCA6IHRoZSBwcmVkaWN0ZWQgdmFsdWVzIG9mIGFsbCB0aGUgYmFzaWMgbWFjaGluZXMgYnVpbHQgb24gJFxtYXRoY2Fse0R9X3trfSQuDQogICAgLSBgcHJlZF90cmFpbjJgIDogdGhlIHByZWRpY3Rpb24gb2YgJFxtYXRoY2Fse0R9X3tcZWxsfSQsIGdpdmVuIGJ5IGFsbCB0aGUgYmFzaWMgbWFjaGllbnMuDQogICAgLSBgb3B0X3BhcmFtZXRlcmAgOiAgdGhlIG9ic2VydmVkIG9wdGltYWwgcGFyYW1ldGVycy4NCiAgICAtIGBtc2VgIDogdGhlIG1lYXMgc3F1YXJlIGVycm9yIGV2YWx1YXRlZCBvbiB0aGUgdGVzdGluZyBkYXRhIGlmIGB0ZXN0X3Jlc3BvbnNlYCBpcyBnaXZlbi4NCiAgICAtIGBrZXJuZWxzYCA6IGEgdmVjdG9yIG9mIGtlcm5lbCBmdW5jdGlvbiB1c2VkLg0KICAgIC0gYGluZF9EMmAgOiBhIGxvZ2ljYWwgdmVjdG9yIGluZGljYXRpbmcgdGhlIHBhcnQgb2YgdHJhaW5pbmcgZGF0YSBjb3JyZXNwb25kaW5nIHRvICRcbWF0aGNhbHtEfV97XGVsbH0kIChgVFJVRWApLg0KICAgIA0KDQpgYGB7cn0NCk1peENvYnJhUmVnIDwtIGZ1bmN0aW9uKHRyYWluX2lucHV0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3ByZWRpY3Rpb25zID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgIG1hY2hpbmVzID0gTlVMTCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2lucHV0ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfbWFjaGluZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IDAuNSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICBpbnZfc2lnbWEgPSBzcXJ0KC41KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwID0gMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9ICJnYXVzc2lhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIG9wdGltaXplTWV0aG9kID0gImdyYWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRCYXNpY01hY2hpbmVQYXJhbSA9IHNldEJhc2ljUGFyYW1ldGVyX01peCgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmFkUGFyYW0gPSBzZXRHcmFkUGFyYW1ldGVyX01peCgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1ldGVyX01peCgpKXsNCiAgIyBidWlsZCBtYWNoaW5lcyArIHR1bmUgcGFyYW1ldGVyDQogIGZpdF9tb2QgPC0gZml0X3BhcmFtZXRlcl9NaXgodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSA9IHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcHJlZGljdGlvbnMgPSB0cmFpbl9wcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hY2hpbmVzID0gbWFjaGluZXMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBzY2FsZV9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX21hY2hpbmUgPSBzY2FsZV9tYWNoaW5lLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gc3BsaXRzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSBuX2N2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gaW52X3NpZ21hLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwID0gYWxwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9IGtlcm5lbHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZU1ldGhvZCA9IG9wdGltaXplTWV0aG9kLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0QmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY01hY2hpbmVQYXJhbSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbSA9IHNldEdyYWRQYXJhbSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbSA9IHNldEdyaWRQYXJhbSkNCiAgIyBwcmVkaWN0aW9uDQogIHByZWQgPC0gcHJlZGljdF9NaXgoZml0dGVkX21vZGVscyA9IGZpdF9tb2QsDQogICAgICAgICAgICAgICAgICAgICAgbmV3X2RhdGEgPSB0ZXN0X2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgIHRlc3RfcmVzcG9uc2UgPSB0ZXN0X3Jlc3BvbnNlKQ0KICByZXR1cm4obGlzdChmaXR0ZWRfYWdncmVnYXRlID0gcHJlZCRqLA0KICAgICAgICAgICAgICBmaXR0ZWRfbWFjaGluZSA9IHByZWQkZml0dGVkX21hY2hpbmUsDQogICAgICAgICAgICAgIHByZWRfdHJhaW4yID0gZml0X21vZCRiYXNpY19tYWNoaW5lcyRmaXR0ZWRfcmVtYWluLA0KICAgICAgICAgICAgICBvcHRfcGFyYW1ldGVyID0gZml0X21vZCRvcHRfcGFyYW1ldGVycywNCiAgICAgICAgICAgICAgbXNlID0gcHJlZCRtc2UsDQogICAgICAgICAgICAgIGtlcm5lbHMgPSBrZXJuZWxzLA0KICAgICAgICAgICAgICBpbmRfRDIgPSBmaXRfbW9kJGJhc2ljX21hY2hpbmVzJGlkMikpDQp9DQpgYGANCg0KLS0tDQoNCj4qKkV4YW1wbGUuNyoqIEEgY29tcGxldGUgYWdncmVnYXRpb24gaXMgaW1wbGVtZW50ZWQgb24gW0FiYWxvbmVdKGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9kYXRhc2V0cy9hYmFsb25lKSBkYXRhc2V0LiBUaHJlZSB0eXBlcyBvZiBiYXNpYyBtYWNoaW5lcywgYW5kIHRocmVlIGRpZmZlcmVudCBrZXJuZWwgZnVuY3Rpb25zIGFyZSB1c2VkLiANCg0KLS0tDQoNCmBgYHtyfQ0KcGFjbWFuOjpwX2xvYWQocmVhZHIpDQpjb2xuYW1lIDwtIGMoIlR5cGUiLCAiTG9uZ2VzdFNoZWxsIiwgIkRpYW1ldGVyIiwgIkhlaWdodCIsICJXaG9sZVdlaWdodCIsICJTaHVja2VkV2VpZ2h0IiwgIlZpc2NlcmFXZWlnaHQiLCAiU2hlbGxXZWlnaHQiLCAiUmluZ3MiKQ0KZGYgPC0gcmVhZHI6OnJlYWRfZGVsaW0oImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy9hYmFsb25lL2FiYWxvbmUuZGF0YSIsIGNvbF9uYW1lcyA9IGNvbG5hbWUsIGRlbGltID0gIiwiLCBzaG93X2NvbF90eXBlcyA9IEZBTFNFKQ0KDQp0cmFpbiA8LSBsb2dpY2FsKG5yb3coZGYpKQ0KdHJhaW5bc2FtcGxlKGxlbmd0aCh0cmFpbiksIGZsb29yKDAuNzUqbnJvdyhkZikpKV0gPC0gVFJVRQ0KDQphZ2cgPC0gTWl4Q29icmFSZWcodHJhaW5faW5wdXQgPSBkZlt0cmFpbiwgMjo4XSwNCiAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSA9IGRmJFJpbmdzW3RyYWluXSwNCiAgICAgICAgICAgICAgICAgICB0ZXN0X2lucHV0ID0gZGZbIXRyYWluLDI6OF0sDQogICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IGRmJFJpbmdzWyF0cmFpbl0sDQogICAgICAgICAgICAgICAgICAgbl9jdiA9IDMsDQogICAgICAgICAgICAgICAgICAgbWFjaGluZXMgPSBjKCJrbm4iLCAicmYiLCAieGdiIiksDQogICAgICAgICAgICAgICAgICAgc3BsaXRzID0gLjUsDQogICAgICAgICAgICAgICAgICAga2VybmVscyA9IGMoImdhdXNzaWFuIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5haXZlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRyaWFuZ3VsYXIiKSwNCiAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZU1ldGhvZCA9IGMoImdyYWQiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyaWQiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyaWQiKSwNCiAgICAgICAgICAgICAgICAgICBzZXRCYXNpY01hY2hpbmVQYXJhbSA9IHNldEJhc2ljUGFyYW1ldGVyX01peChrID0gYygyLDUsNywxMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gMjo1KjEwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kc194Z2IgPSBjKDEsMyw1KSoxMDApLA0KICAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbSA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KHJhdGUgPSAibGluZWFyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2VmX2F1dG8gPSBjKDAuNSwgMC41KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9zdGVwID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9yZXN1bHQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpZ3VyZSA9IFRSVUUpLA0KICAgICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbSA9IHNldEdyaWRQYXJhbWV0ZXJfTWl4KHBhcmFtZXRlcnMgPSBsaXN0KGFscGhhID0gc2VxKDAuMDAwMSwgMywgbGVuZ3RoLm91dCA9IDIwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJldGEgPSBzZXEoMC4wMDAxLCA1LCBsZW5ndGgub3V0ID0gMjApKSkpDQpzcXJ0KGFnZyRtc2UpDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij5SZWZlcmVuY2VzPC9zcGFuPnstfQ0KPT09DQoNCi0tLQ0KDQotIFtCaWF1IGV0IGFsLiAoMjAxNildKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAwNDcyNTlYMTUwMDA5NTApDQotIFtIYXMgKDIwMjEpXShodHRwczovL2hhbC5hcmNoaXZlcy1vdXZlcnRlcy5mci9oYWwtMDI4ODQzMzN2NSkNCi0gLi4uDQotIFtkcGx5ciB2aWRlb3NdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2hhc2h0YWcvZHBseXIpIGByIGZvbnRhd2Vzb21lOjpmYSgidmlkZW8iKWANCi0gW2dncGxvdDIgdmlkZW8gdHV0b3JpYWxdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2hhc2h0YWcvZ2dwbG90MikgYHIgZm9udGF3ZXNvbWU6OmZhKCJ2aWRlbyIpYA0KLSBbUiBmb3IgZGF0YSBzY2llbmNlXShodHRwczovL3I0ZHMuaGFkLmNvLm56LykNCg0KLS0tDQoNCg==